文章目录
1. 声明式事务概念
1.1 声明式事务
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免直接进行事务操作。
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
区别:
- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。
1.2 Spring事务管理器
-
Spring声明式事务对应依赖
- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
-
Spring声明式事务对应事务管理器接口
现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现。
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
2. 基于注解的声明式事务
2.1 基本事务控制
-
配置事务管理器
数据库相关的配置@Configuration @ComponenScan("com.start") @PropertySource(value = "classpath:jdbc.properties") @EnableTransactionManagement public class DataSourceConfig { /** * 实例化dataSource加入到ioc容器 * @param url * @param driver * @param username * @param password * @return */ @Bean public DataSource dataSource(@Value("${jdbc.url}")String url, @Value("${jdbc.driver}")String driver, @Value("${jdbc.username}")String username, @Value("${jdbc.password}")String password){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } /** * 实例化JdbcTemplate对象,需要使用ioc中的DataSource * @param dataSource * @return */ @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } /** * 装配事务管理实现对象 * @param dataSource * @return */ @Bean public TransactionManager transactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
-
使用声明事务注解@Transactional
@Service public class StudentService { @Autowired private StudentDao studentDao; @Transactional public void changeInfo(){ studentDao.updateAgeById(100,1); System.out.println("-----------"); int i = 1/0; studentDao.updateNameById("test1",1); } }
2.2 事务属性:只读
-
只读介绍
对一个查询操作来说,如果把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。 -
设置方式
// readOnly = true把当前事务设置为只读 默认是false! @Transactional(readOnly = true)
-
针对DML动作设置只读模式
会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
-
@Transactional注解放在类上
-
生效原则
@Transactional 注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了 @Transactional 注解。
方法离最近的 @Transactional 注解中的事务属性设置生效。
-
用法举例
在类级别@Transactional注解中设置只读,类中所有的查询方法都不需要设置@Transactional注解。
对增删改方法设置@Transactional注解 readOnly 属性为 false。
@Service @Transactional(readOnly = true) public class EmpService { @Transactional(readOnly = false) public void updateTwice(……) { …… } // readOnly = true把当前事务设置为只读 // @Transactional(readOnly = true) public String getEmpName(Integer empId) { …… } }
-
2.3 事务属性:超时时间
-
设置超时时间
@Service public class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! */ @Transactional(readOnly = false,timeout = 3) public void changeInfo(){ studentDao.updateAgeById(100,1); //休眠4秒,等待方法超时! try { Thread.sleep(4000); } catch (InterruptedException e) { throw new RuntimeException(e); } studentDao.updateNameById("test1",1); } }
2.4 事务属性:事务异常
-
默认情况
默认只针对运行时异常回滚,编译时异常不回滚。
@Service public class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内 */ @Transactional(readOnly = false,timeout = 3) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); } }
-
设置回滚异常
rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚
/** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! */ @Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); }
-
设置不回滚的异常
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
@Service public class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! */ @Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); } }
2.5 事务属性:事务隔离级别
-
事务隔离级别
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。
常见的隔离级别:
- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
-
事务隔离级别设置
import com.atguigu.dao.StudentDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class StudentService { @Autowired private StudentDao studentDao; /** * isolation = 设置事务的隔离级别,mysql默认是repeatable read! */ @Transactional(readOnly = false, timeout = 3, rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class, isolation = Isolation.REPEATABLE_READ) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); } }
2.6 事务属性:事务传播行为
-
propagation属性
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:
Propagation propagation() default Propagation.REQUIRED;
propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:
名称 含义 REQUIRED 默认值 如果父方法有事务,就加入,如果没有就新建自己独立 REQUIRES_NEW 不管父方法是否有事务,都新建事务,都是独立的 -
测试
-
声明两个业务方法
@Service public class StudentService { @Autowired private StudentDao studentDao; @Transactional(readOnly = false, timeout = 3, rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class, isolation = Isolation.REPEATABLE_READ) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); } /** * 声明两个独立修改数据库的事务业务方法 */ @Transactional(propagation = Propagation.REQUIRED) public void changeAge(){ studentDao.updateAgeById(99,1); } @Transactional(propagation = Propagation.REQUIRED) public void changeName(){ studentDao.updateNameById("test2",1); int i = 1/0; } }
-
声明一个整合业务方法
@Service public class TopService { @Autowired private StudentService studentService; @Transactional public void topService(){ studentService.changeAge(); studentService.changeName(); } }
-
添加传播行为测试
@SpringJUnitConfig(classes = AppConfig.class) public class TxTest { @Autowired private StudentService studentService; @Autowired private TopService topService; @Test public void testTx() throws FileNotFoundException { topService.topService(); } }
注意:
在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。
-