Spring事物失效的场景以及事物的另类玩法

开篇

  关于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);
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值