SpringFramework-声明式事务
声明式事务概念编程式事务手动编写程序来管理事务即通过编写代码的方式直接控制事务的提交和回滚。编程式事务的主要优点是灵活性高可以按照自己的需求来控制事务的粒度、模式等等。try { // 开启事务关闭事务的自动提交 conn.setAutoCommit(false); // 核心操作 // 业务代码 // 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally{ // 释放数据库连接 conn.close(); }编程式的实现方式存在缺陷细节没有被屏蔽[比较繁琐]代码复用性不高[每次实现功能都需要编写]。声明式事务使用注解或 XML 配置的方式来控制事务的提交和回滚。 开发者只需要添加配置即可 具体事务的实现由第三方框架实现。将事务的控制和业务逻辑分离开来Spring事务管理器Spring声明式事务对应依赖spring-tx: 包含声明式事务实现的基本规范事务管理器规范接口和事务增强等等spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManagerspring-orm: 包含其他持久层框架的事务管理器实现类例如Hibernate/Jpa等Spring声明式事务对应事务管理器接口要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager将来整合JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现 DataSourceTransactionManager类中的主要方法doBegin()开启事务doSuspend()挂起事务doResume()恢复挂起的事务doCommit()提交事务doRollback()回滚事务基于注解的声明式事务基本事务控制在配置类中选择合适的事务管理器交给IoC容器同时开启事务注解的支持Configuration ComponentScan(com.atguigu) PropertySource(classpath:jdbc.properties) //EnableAspectJAutoProxy //开启aspectj注解的支持 EnableTransactionManagement //开启事务注解的支持 public class JavaConfig { Value(${atguigu.driver}) private String driver; Value(${atguigu.url}) private String url; Value(${atguigu.username}) private String username; Value(${atguigu.password}) private String password; //druid连接池 Bean public DataSource dataSource(){ DruidDataSource dataSource new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } Bean //jdbcTemplate public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } Bean public TransactionManager transactionManager(DataSource dataSource){ //内部要进行事务的操作基于的连接池 DataSourceTransactionManager dataSourceTransactionManager new DataSourceTransactionManager(); //需要连接池对象 dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }使用声明事务注解Transactional添加事务Transactional位置 方法 | 类上方法 当前方法有事务类上 类下的所有方法都有事务Transactional public void changeInfo(){ studentDao.updateAgeById(100,1); System.out.println(-----------); int i 1/0; studentDao.updateNameById(test1,1); }事务属性只读只读模式可以提升查询事务的效率 推荐事务中只有查询代码使用只读模式默认 boolean readOnly() default false;解释 一般情况下都是通过类添加注解添加事务类下的所有方法都有事务查询方法可以通过再次添加注解设置为只读模式提高效率!// readOnly true把当前事务设置为只读 默认是false! Transactional(readOnly true)Transactional(readOnly true) public class EmpService { // 为了便于核对数据库操作结果不要修改同一条记录 Transactional(readOnly false) public void updateTwice(……) { …… } }事务属性超时时间默认 永远不超时 -1设置 timeout 时间 秒数 超过时间就会回滚事务和释放异常 TransactionTimedOutException如果类上设置事务属性方法也设置了事务注解方法不会生效方法上的注解覆盖了类上的注解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); } }事务属性事务异常指定异常回滚和指定异常不回滚默认情况下指定发生运行时异常事务才会回滚我们可以指定Exception异常来控制所有异常都回滚rollbackFor Exception.classnoRollbackFor 回滚异常范围内控制某个异常不回滚Transactional(rollbackFor Exception.class , noRollbackFor FileNotFoundException.class) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(99,1); new FileInputStream(xxxx); studentDao.updateNameById(test3,1); }事务属性事务隔离级别数据库事务的隔离级别是指在多个事务并发执行时数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括1. 读未提交Read Uncommitted事务可以读取未被提交的数据容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全一般不用。2. 读已提交Read Committed事务只能读取已经提交的数据可以避免脏读问题但可能引发不可重复读和幻读。[推荐]3. 可重复读Repeatable Read在一个事务中相同的查询将返回相同的结果集不管其他事务对数据做了什么修改。可以避免脏读和不可重复读但仍有幻读的问题。[默认]4. 串行化Serializable最高的隔离级别完全禁止了并发只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题但效率较低不适用于高并发场景。脏读一个事务读取了另一个事务尚未提交的数据同时第一个事务发生回滚数据库出现数据不匹配不可重复读同一个事务内多次读取同一条记录由于其他事务对该记录进行了修改并提交导致前后读取结果不一致幻读同一个事务内多次执行同一个查询条件由于其他事务插入了满足条件的新记录并提交导致前后返回的结果集数量不一致推荐设置第二个隔离级别isolation Isolation.READ_COMMITTEDTransactional(isolation Isolation.READ_COMMITTED) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(99,1); new FileInputStream(xxxx); studentDao.updateNameById(test3,1); }事务属性事务传播行为3. Propagation.NESTED如果当前存在事务则在该事务中嵌套一个新事务如果没有事务则与Propagation.REQUIRED一样。4. Propagation.SUPPORTS如果当前存在事务则加入该事务否则以非事务方式执行。5. Propagation.NOT\_SUPPORTED以非事务方式执行如果当前存在事务挂起该事务。6. Propagation.MANDATORY必须在一个已有的事务中执行否则抛出异常。7. Propagation.NEVER必须在没有事务的情况下执行否则抛出异常。/** * 声明两个独立修改数据库的事务业务方法 * * propagation Propagation.REQUIRED 父方法有事务我们就加入到父方法的事务 * 最终是同一个事务 推荐使用默认值 * * propagation Propagation.REQUIRES_NEW * 不管父方法是否有事务我都是独立的事务 * 两个事务或者三个事务 * */ Transactional(propagation Propagation.REQUIRES_NEW) public void changeAge(){ studentDao.updateAgeById(998,1); } Transactional(propagation Propagation.REQUIRES_NEW) public void changeName(){ studentDao.updateNameById(二狗子,1); int i 1/0; //报错 }Service public class TopService { Autowired private StudentService studentService; Transactional public void topService(){ studentService.changeAge(); studentService.changeName(); } }在同一个类中对于Transactional注解的方法调用事务传播行为不会生效。这是因为Spring框架中使用代理模式实现了事务机制在同一个类中的方法调用并不经过代理而是通过对象的方法调用因此Transactional注解的设置不会被代理捕获也就不会产生任何事务传播行为的效果。