开篇
关于Spring 事物的用法,网上已有很多大神进行说明了,我这就不再赘述了,这篇文章我们来玩点别的,大家有什么好的玩法和建议,还望不吝赐教。
事务失败的场景
别人都是怎么使用事物,我在这里说怎么整坏事物,有点添堵的感觉,话虽如此,我还是说得说二句。
无论是声明式事务还是编程式事务,他们实际上都是利用线程私有类来保存Connection,所以,如果我们直接在事务中通过DataSource或原生的方式获取Connection或者在多线程环境中进行数据库操作的时候,因为此时Connection并没有通过事务管理器所管理,所以其事务会失效。
失败示例一:多线程环境
该示例是基于JdbcTemplate做的示例。
@Test
public void testTransactionFail() throws SQLException {
ApplicationContext ctx = new AnnotationConfigApplicationContext(TxConfig.class);
// 获取需要事物控制的service
StudentServiceTransaction studentServiceTransaction =
ctx.getBean(StudentServiceTransaction.class);
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
// 设置读已提交的隔离级别
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置事物的传播属性
transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置是否是只读模式
transactionDefinition.setReadOnly(false);
// 设置超时时间为30s
transactionDefinition.setTimeout(30);
// 获取配置好的事务管理器
PlatformTransactionManager transactionManager = ctx.getBean(PlatformTransactionManager.class);
// 根据transactionDefinition获取事物状态
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
Student student = new Student();
// 测试在另一个线程中使用事务
Thread thread = new Thread(() -> {
try {
student.setName("11111111111111");
// 因为在一个新线程中获取的Connection不在事务管理器中管理,所以其不支持事务
studentService.insert(student);
studentService.insert(student);
int i = 1 / 0; // 即使抛出异常执行也会成功
} catch (Throwable e) {
e.printStackTrace();
}
});
thread.start();
thread.join();
// 因为使用jdbcTemplate,所以下面的两条语句都在事物管理范围之内
student.setName("2222222222222222");
studentService.insert(student);
studentService.insert(student);
int i = 1 / 0; // 测试抛出异常
} catch (Throwable ex) {
// 只要抛出异常就rollback
transactionManager.rollback(status);
throw new RuntimeException("TransactionCallback threw undeclared checked exception", ex);
}
// 提交
transactionManager.commit(status);
}
该程序运行的结果是新线程中的insert语句并没有使用事务管理器进行管理,从而即使抛出异常的情况下,只要insert语句已经执行完毕,数据还是报错成功哈。如果没有成功请查看是不是踩到了JdbcTemplate中的坑了[后续文章介绍],简单的说就是因为JdbcTemplate不会即使在close连接的情况下,也不会太commit事物,所以如果autocommit没有为true的情况下,那么数据还是不会提交成功。
失败示例二:另起Connection
下面展示一下如果该Connection对象不是事务管理器所管理的对象,那么该Connection所对应的操作,就不受事务管理器所影响。
@Test
public void testDefaultTransactionDefinition() throws SQLException {
…
// 根据transactionDefinition获取事物状态
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
Student student = new Student();
DataSource dataSource = ctx.getBean(DataSource.class);
try (Connection connection = dataSource.getConnection();) {
// 这两个语句在事物管理中,所以即使后面抛出异常也会执行成功
student.setName("11111111111111111");
insert(connection, student);
insert(connection, student);
} catch (Exception e) {
}
// 因为使用jdbcTemplate,所以下面的两条语句都在事物管理范围之内
student.setName("2222222222222222");
studentService.insert(student);
studentService.insert(student);
int i = 1 / 0; // 测试抛出异常
} catch (Throwable ex) {
// 只要抛出异常就rollback
transactionManager.rollback(status);
throw new RuntimeException("TransactionCallback threw undeclared checked exception", ex);
}
// 提交
transactionManager.commit(status);
}
// 为了测试这里使用原生jdbc的方式来操作数据库
private int insert(Connection connection, Student student) throws SQLException {
PreparedStatement statement =
connection.prepareStatement("insert into student (id,name) values (?,?)");
statement.setObject(1, student.getId());
statement.setString(2, student.getName());
int executeUpdate = statement.executeUpdate();
statement.close();
return executeUpdate;
}
非JdbcTemplate下使用Spring事物
上面介绍了事物失败的场景,那么接下来介绍一下自己搞起一个事物。
JdbcTemplate在不需要额外配置的情况下就可以使用Spring事物,其原理实际上就是利用DataSourceUtils来getConnection()方法获取和releaseConnection()方法释放连接[请移步JdbcTemplate原理简介相关章节]。而实际上,对于MyBatis环境中,是因为SqlSessionFactory默认使用SpringManagedTransactionFactory来管理事物,其原理也是利用DataSourceUtils的这两个方法。
service类编写:
@Service
public class StudentServiceTransaction {
@Autowired
private DataSource dataSource;
public int insert(Student student) {
Connection conn = null;
int result = 0;
try {
conn = getConnection();
result = insert(conn, student);
} catch (Exception e) {
// 尽量提前释放资源
if (conn != null)
release(conn);
throw new RuntimeException("数据异常", e);
} finally {
// 最后要释放该连接
if (conn != null)
release(conn);
}
return result;
}
// 获取Connection对象
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
// 释放Connection对象,这里不一定就是close了哈
private void release(Connection connection) {
DataSourceUtils.releaseConnection(connection, dataSource);
}
private int insert(Connection connection, Student student) throws SQLException {
PreparedStatement statement = connection
.prepareStatement("insert into student (id,name) values (?,?)");
statement.setObject(1, student.getId());
statement.setString(2, student.getName());
int executeUpdate = statement.executeUpdate();
statement.close();
return executeUpdate;
}
}
测试:这里以编程式事物的方式来进行测试,当然StudentServiceTransaction也支持声明式事物哈,这里不作演示。
@Test
public void testCustomerTran() throws SQLException {
…//省略前置代码
// 获取需要事物控制的service
StudentServiceTransaction studentServiceTransaction =
ctx.getBean(StudentServiceTransaction.class);
// 根据transactionDefinition获取事物状态
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
Student student = new Student();
// 因为使用jdbcTemplate,所以下面的两条语句都在事物管理范围之内
student.setName("333333333333333333");
studentServiceTransaction.insert(student);
studentServiceTransaction.insert(student);
int i = 1 / 0; // 测试抛出异常
} catch (Throwable ex) {
// 只要抛出异常就rollback
transactionManager.rollback(status);
throw new RuntimeException("TransactionCallback threw undeclared checked exception", ex);
}
// 提交
transactionManager.commit(status);
}