一、事务的定义事务(Transaction),是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit),是恢复和并发控制的基本单位。 事务的产生,其实是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题. 二、事务的属性事务具有4个属性,简称
三、Spring 事务的隔离级别当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。 在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题 3.1 隔离级别引出的问题3.1.1 脏读
3.1.2 不可重复读
不可重复读侧重修改。 3.1.3 幻读
幻读侧重新增或者删除。 3.2 隔离级别在理想状态下,事务之间将完全隔离(即下表中的 然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。 完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
四、Spring 事务的传播机制Spring 事务的传播机制描述了在嵌套事务当中,当前事务与外部事务(最近的那个,有可能没有)的继承关系。 比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。 Spring 事务的传播有如下机制
五、Spring 事务的应用(声明式)Spring 声明式事务是指依托注解 5.1 事务只读从事务开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
5.1.1 应用场景在诸如统计查询、报表查询的过程当中,需要多次查询,为了避免在查询过程当中对剩余查询数据的修改,保证数据整体在某一时刻的一致性,需要使用只读事务。 5.1.2 使用方式@Transactional(propagation = Propagation.REQUIRES, readOnly = true) public ListfindAllProducts() { return this.productDao.findAllProducts(); } 5.2 事务回滚在事务注解 5.2.1 使用方式// 回滚Exception类型异常 @Transactional(rollbackFor = Exception.class) public void test1() throws Exception { // .. } // 回滚自定义类型异常 @Transactional(rollbackForClassName = "org.transaction.demo.CustomException") public void test2() throws Exception { // .. } // 不回滚自定义类型异常 @Transactional(noRollbackFor = CustomException.class) public void test3() throws Exception { // .. } 5.3 事务超时如果一个事务长时间占用数据库连接,会导致服务等待从而引起服务雪崩效应,所以设置一个合理的超时时间,是必要的。默认不超时。事务超时会引起事务回滚。
5.3.1 使用方式//设置事务超时时间,单位秒 @Transactional(timeout = 5) public void test() { // .. } 5.4 事务传播机制的使用方式//每次外层事务调用都会开启一个新事务 @Transactional(propagation = Propagation.REQUIRES_NEW) public void test() { // .. } 5.5 事务隔离机制的使用方式
//设置事务隔离级别为串行 @Transactional(isolation = Isolation.SERIALIZABLE)) public void test() { // .. } 六、Spring 声明式事务的 AOP 陷阱总所周知,声明式事务依托 6.1 AOP 代理陷阱复现@Transactional(rollbackFor = RuntimeException.class) public void insertUser(User user) { userMapper.insertUser(user); throw new RuntimeException(""); } /** * 内部调用新增方法 */ public void insertMale(User user) { user.setGender("male"); this.insertUser(user); } 当外部方法直接调用 6.2 原因分析AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部调用使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。 6.2.1 伪代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理之前做增强 System.out.println("代理之前..."); //根据需要添加事务处理逻辑 ... //调用原有方法 insertMale Object obj = method.invoke(object, args); //做增强 System.out.println("代理之后..."); //根据需要添加事务处理逻辑 ... return obj; } 当执行
public void insertMale(User user) { user.setGender("male"); //这里的 this 指向了目标类而不是代理类 //所以及时下面的方法添加了事务注解,但是并没有除法增强实现,事务也还是不生效的 this.insertUser(user); } 6.3 解决方案6.3.1 注入自身利用Spring可以循环依赖来解决问题 @Service public class TestService { @Autowired private TestService testService; @Transactional(rollbackFor = RuntimeException.class) public void insertUser(User user) { userMapper.insertUser(user); throw new RuntimeException(""); } /** * 内部调用新增方法 */ public void insertMale(User user) { user.setGender("male"); //这里使用 字段 testService 调用事务方法 testService.insertUser(user); } } 6.3.2 使用 ApplicationContext 获取目标类注入 Spring 上下文 @Service public class TestService { @Autowired private ApplicationContext applicationContext; @Transactional(rollbackFor = RuntimeException.class) public void insertUser(User user) { userMapper.insertUser(user); throw new RuntimeException(""); } /** * 内部调用新增方法 */ public void insertMale(User user) { user.setGender("male"); //这里使用上下文获取目标类实例 TestService testService = applicationContext.getBean(TestService.class); testService.insertUser(user); } } 6.3.3 使用 AopContextAop 上下文采用 使用时候要在启动类上添加
@SpringBootApplication //配置:导出代理对象到AOP上下文 @EnableAspectJAutoProxy(exposeProxy = true) public class DemoApplication { }
public class TestService { @Transactional(rollbackFor = RuntimeException.class) public void insertUser(User user) { userMapper.insertUser(user); throw new RuntimeException(""); } /** * 内部调用新增方法 */ public void insertMale(User user) { user.setGender("male"); //使用AOP上下文获取目标代理类 TestService testService = (TestService) AopContext.currentProxy(); testService.insertUser(user); } www.jcdi.cn }www.sobd.cc |
|