本篇在前面几篇的基础上,再来聊一下数据库相关操作经常会涉及的事务问题与多数据源支持问题。
在大部分涉及到数据库操作的项目里面,事务控制、事务处理都是一个无法回避的问题。得益于Spring框架的封装,业务代码中进行事务控制操作起来也很简单,直接加个@Transactional注解即可,大大简化了对业务代码的侵入性。那么对@Transactional事务注解了解的够全面吗?知道有哪些场景可能会导致@Transactional注解并不会如你预期的方式生效吗?知道应该怎么使用@Transactional才能保证对性能的影响最小化吗?
下面我们一起探讨下这些问题。
先看下JDBC的事务处理
基于JDBC进行数据库操作的时候,如果需要进行事务的控制与处理,整体的一个处理流程如下图所示:
其中蓝色的部分为需要开发人员去进行实现的,也即JDBC场景下的事务保护与处理,整个事务过程的处理都是需要开发人员进行关注与处理的。
按照这个流程的逻辑,写一下对应的实现代码:
public void testJdbcTransactional(DataSource dataSource) { Connection conn = null; int result = 0; try { // 获取链接 conn = dataSource.getConnection(); // 禁用自动事务提交,改为手动控制 conn.setAutoCommit(false); // 设置事务隔离级别 conn.setTransactionIsolation( TransactionIoslationLevel.READ_COMMITTED.getLevel() ); // 执行SQL PreparedStatement ps = conn.prepareStatement("insert into user (id, name) values (?, ?)"); ps.setString(1, "123456"); ps.setString(2, "Tom"); result = ps.executeUpdate(); // 执行成功,手动提交事务 conn.commit(); } catch (Exception e) { // 出现异常,手动回滚事务 if (conn != null) { try { conn.rollback(); } catch (Exception e) { // write log... } } } finally { // 执行结束,最终不管成功还是失败,都要释放资源,断开连接 try { if (conn != null && !conn.isClosed()) { conn.close(); } } catch (Exception e) { // write log... } } }
Spring声明式事务处理机制
Spring数据库事务约定处理逻辑流程如下:
对比上一章节的JDBC的事务处理,Spring场景下,事务的处理操作交给了Spring框架处理,开发人员仅需要实现自己的业务逻辑即可,大大简化了事务方面的处理投入。
基于Spring事务机制,实现上述DB操作事务控制的代码,可以按照如下方式:
@Transactional public void insertUser() { userDao.insertUser(); }
与JDBC事务实现代码相比,基于Spring的方式只需要添加一个@Transactional注解即可,代码中只需要实现业务逻辑即可,实现了事务控制机制对业务代码的低侵入性。
Spring支持的基于Spring AOP实现的声明式事务功能,所谓声明式事务,即使用@Transactional注解进行声明标注,告诉Spring框架在什么地方启用数据库事务控制能力。@Transactional注解,可以添加在类或者方法上。如果其添加在类上时,表明此类中所有的public非静态方法都将启用事务控制能力。
@Transactional注解说明
主要可选配置
readOnly
指定当前事务是否为一个只读事务。设置为true标识此事务是个只读事务,默认情况为false。
只读事务
在多条查询语句一起执行的场景里面会涉及到的概念。表示在事务设置的那一刻开始,到整个事务执行结束的过程中,其他事务所提交的写操作数据,对该事务都不可见。
举个例子:
现在有一个复合查询操作,包含2条SQL查询操作:先获取用户表count数,再获取用户表中所有数据。
执行过程:
(1) 先执行完获取用户表count数,得到结果10
(2) 在还没开始执行后一条语句的时候,另一个进程操作了DB并往用户表中插入一条新数据
(3) 复合操作的第二条SQL语句,获取用户