1)什么是事务?为什么需要事务?
事务就是将一组操作封装成一个原子执行单元,要么全部成功,要么全部失败,简单来说:事务诞生的目的就是为了把若干个独立的操作给打包成一个整体,保证业务稳定性;
原子性,一致性,持久性,隔离性;
比如说之前在Spring环境中整合MyBatis来做数据库的增删改查操作,在正常情况下操作数据库是没有问题的,但是当一个业务需要并发式的数据库,而且需要设计到修改操作,插入操作,删除操作就会存在问题,比如说转账业务业务,其实一共有两个步骤,第一从账户里面扣钱,第二部从账户里面加钱
如果第一步顺利执行的,在执行完成第二步前,程序发生了异常, 此时第二个操作就不能够正常执行了,这就会是很严重的事故了,致使 A 的钱少了,但 B 的钱没有增多,无论是用户还是我们都是不能容忍这样的 bug 的
那也就是在程序发生异常的时候,回滚所有已经成功数据库操作,这样就算这一次转账失败了,也不会给客户和商家带来损失
Spring 中事务的作用是保证在数据层或业务层执行的一系列数据库操作同成功或同失败
2)事务是如何执行的?如何实现的?如何保证一致性?要么全部成功?转账操作
1)比如说现在有两台服务器正在进行转账操作,服务器A有100元,服务器B有100元,A这是正在给B进行转账操作,A给B转了50元钱,但是此时公司的电线被挖掘机铲断了或者掉电了
2)一致性表示的是A和B的钱数之和不能超过200元;
3)实现一致性和完整性,本质上依靠的是日志,只有事务的开启和事务的执行,等到下一次恢复的时候,会在日志里面自检,掉电也没事,会自动执行补偿机制;
如果没有事务的机制,那么在上面转账的过程中如果出现了掉电情况,没有事务就不可以回滚,就会出现程序错误,那么此时张三的钱就会少了一半;
在MYSQL中操作事务有三种方式:
1)开启事务:start transaction
2)提交事务:commit;
3)回滚事务:rollback;
3)编程式事务:通过手动写代码来操作事务,两种方案
开启事务
提交事务
回滚事务
1)通过TransactionTemplate来进行使用操作事务,需要通过先将TransactionTemplate注入到当前类中
2)然后再进行使用TransactionTemplate对象的execute方法(和线程池类似)执行事务并返回相应的执行结果,如果在程序中出现了异常·,那么我们可以直接通过代码进行手动回滚事务
3)直接进行调用TransactionTemplate对象的execute方法开启事务,在catch语句块里面里面使用TransactionStatus对象的setRollbackOnly方法来回滚事务,和业务代码耦合
@Autowired private TransactionTemplate transactionTemplate; @RequestMapping("/Java100") @ResponseBody @Transactional public int add(User user){ if(user==null||!StringUtils.hasLength(user.getUsername())||!StringUtils.hasLength(user.getPassword())){ return -1; } return transactionTemplate.execute(status -> { int result=0; try{ result= userMapper.Insert(user); }catch (Exception e){ //手动进行回滚事务,如果说最后出现了异常 status.setRollbackOnly(); } return result; }); }
2)第二种方式就是通过注入TransactionManger对象和TransactionDefinition对象
1)通过两者的相互配合获取到事务,调用TransactionManger的getTransaction方法里面传入TransactionDefinition对象来进行获取到事务,返回一个事务对象是TransactionStatus
2)后续的操作都是通过TransactionManger进行操作TransactionStatus来进行回滚事务,操作事务的
3)SpringBoot 内置了两个对象,DataSourceTransactionManager⽤来获取事务(开启事务,提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus;
@Controller
public class AppController{
@Autowired
private DataSourceTransactionManager manger;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/hello")
@ResponseBody
public void run(){
//前端参数校验
//1.开启事务,获取事务
TransactionStatus status=manger.getTransaction(transactionDefinition);
//2.调用方法,可以是数据库操作
//3.进行回滚
manger.rollback(status);
manger.commit(status);
}
}
手动操作事务(了解),编程式事务,灵活性更高,可以在任意的代码片段上面加上事务操作
Spring进行操作事务和上面的MYSQL相似,也是需要手动开启事务,提交事务,回滚事务,但是写法比较麻烦;
4)Spring中声明式事务的实现:
@Transaction的作用范围:即可以修饰类也可以修饰方法
1)修饰方法:更推荐使用,只能应用于public方法上面,否则不生效
2)修饰类:表明该注解针对于类中的所有的public方法都生效
1)声明式自动提交事务,利用注解自动进行提交和回滚事务;
2)MYSQL中,事务是只有一个方法的,但是在Spring里面是有多个方法的,可能在UserController里面调用两个service方法,就会十分复杂,多个事务会在同时执行;
1)开启事务:start transaction
2)执行业务逻辑
3)提交事务:commit
4)回滚事务rollback
1)声明式事务的实现是很简单的,只需要在需要的方法上面加上@Transactional注解就可以进行实现了,我们是无需进行手动开启事务和提交事务
2)在进入方法的时候之前会自动开启事务的,在方法执行完之后会自动提交事务,如果说中途发生了没有进行处理的异常会自动回滚事务;
最终结果:向数据库中插入数据成功
1)上面的这段代码向数据库进行插入操作在代码出现异常之前,向数据库成功的进行插入操作插入成功,但是代码还是会报错,因为在插入数据完毕之后还是会出现了异常;
2)但是在InsertDemo方法上加上@Transactional这个注解之后,我们虽然还是插入操作在出现异常操作的前面,但是方法执行完,但是还是会插入失败,因为事务是可以进行回滚的;
3)加上@Transaction的注解的方法,不需要手动开启事务和提交事务和回滚事务了,执行方法的时候,进入方法之前会自动进行开启事务,执行完方法之后会自动提交事务,如果发生异常没有try catch包裹处理异常会自动回滚事务,因为你如果写了trycatch语句块之后,程序认为你有能力处理这个异常,就不会自动回滚事务;
@Transaction作用域的:可以修饰方法或者类
1)当这个注解在修饰方法的时候,他是只能应用于public方法上面的,否则是不会生效的
2)在进行修饰类的时候,表名该注解对当前类的所有的public方法都生效,都会自动开启提交或者回滚事务因为@Trancation作用范围是public方法,其他修饰符修饰的方法是不生效的
3)之前我们应用于@Transaction注解是应用于单元测试,单元测试类加上@Transcation注解会自动进行回滚,不会污染数据库;
try catch导致事务失效的集中解决方案:@Transaction在被异常捕获的情况下,不会进行事务的自动回滚;
0)加了@Transaction注解方法中的代码表示出现了异常就会进行回滚,但是@transaction在异常被捕获的情况下,不会进行事务自动回滚,如果进行进行了try catch,即使出现了异常,也没有自动回滚,这是归结于Spring的设计思想了,因为Spring是认为你有能力有义务去解决这个异常的,就不会自动进行事务的提交和回滚,Spring认为你的程序是正常的,虽然代码出现了错误,但是你加了try catch之后,Spring仍然认为程序员开发者已经意识到这个问题了,程序员有能力有义务发现这个问题,并已经解决了这个问题,不会出现数据的回滚;
1)throw直接抛出异常,直接在捕获异常的代码的catch语句中写throw e,就可以进行解决事务失效的问题;
2)在catch里面使用代码手动进行事务的回滚操作,我们在catch捕获异常语句中可以直接进行获取到TransactionAspectSupport.currentTransactionStatus()可以得到当前的事务,然后我们调用里面的回滚方法setRollbackOnly()方法就可以实现回滚事务了
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //我们写上这一句代码就可以自动的在trycatch异常中执行我们的事务回滚操作了
5)@Transcation其他的参数配合使用:
1)在@Transaction注解里面还可以进行设置Spring的隔离级别:
@Transactional(isolation = Isolation.DEFAULT,timeout =60)
2)timeout:表示事务的超时时间,如果说超过该时间限制,但是事务还是没有正常完成,将自动回滚该事务,默认值是-1;
3)noRollbackFor的时候,使用上面两种方法也是不会进行回滚事务的
@Resource MyBatisMapper myBatisMapper; @Transactional(isolation = Isolation.DEFAULT,timeout =60) public int InsertDemo(int userID,int classID,String username,String password) { int a= myBatisMapper.InsertDemo(userID,classID,username,password); try { int b = 10 / 0; }catch (ArithmeticException e) { e.printStackTrace(); } return a; } 上面这段代码是不会出现回滚的,最终我们还是向数据库中成功地进行了插入数据了
来进行看一下@Transaction里面常用的参数:
1)value和TRansactionManger的用法是一样的,在进行编程式事务的时候,进行创建了多个DataSourceTransactionManager对象的时候,可以进行指定使用哪一个任务管理器
2)progation:当有嵌套式的事务的时候,它的一个行为模式的,一共有七个属性,多个方法出现了嵌套事务的时候,行为模式,就叫做事物的传播机制
3)readOnly:加快事务执行效率,主要是指定是否为只读事务,默认值是false,也就是说咱们的默认指定的事务不是只读事务,只读事务,表示这个事务只能进行读取,不能进行其他操作
3.1)接下来想一下,多个线程只进行读取数据,是不会涉及到线程安全的问题的,只有涉及到修改/删除/新增的时候,才会涉及到线程安全问题,那么这个时候问题就来了?
就可以进行使用ReentrantLock和synchronized锁了,也可以使用ReadWriteLock,有什么区别呢?
我们使用的目的就是为了让读和写进行分离,可以让我们的维度变得更加清晰,ReadWriteLock虽然是有加锁操作的,但是不会真的加锁,所以说ReadWriteLock就可以很大程度上提高程序的运行效率和执行性能,虽然也是加锁,但是可以让读读操作不会进行上锁操作,不会产生互斥,读读操作就是可以并发执行的,不会互斥,不会加锁操作,因为操作数据的时候有50%的概率是在读取查询数据,剩下的就是在修改数据,当有多个线程共同修改同一个全局变量的时候才会发生线程安全问题;
3.2)只读事务和非只读事务的作用,也是这样的也是为了提高事务的执行效率
如果是一个只读事务,那么,它只会进行读取操作,那么对于只读事务,就可以不用去,开启事务,提交事务,回滚事务,因为读操作不会修改业务数据不会导致程序出现错误,或者不加@Transaction也是可以的;2)timeout:表示事务的超时时间,如果说超过该时间限制,但是事务干的事还是没有正常完成,将自动回滚该事务,以秒为单位,默认值是-1,是没有时间限制的;
3)rollbackFor(类型)和rollbackForClassName(String类型):能够导致回滚异常类
4)noRollbackFo(类型)和noRollbackFor ClassName(String类型)不能够导致回滚的异常类
5)@Transaction的工作原理:
在Spring4.1之后,Spring动态代理都是使用的是高性能的CGLIB来实现的,只有CGLIB搞不定了,才会使用JDK动态代理实现的
1)咱们的@Transaction是基于AOP来进行实现的,对统一问题作集中的处理,AOP又是基于动态代理来进行实现的;
1.1)如果咱们的目标对象实现了接口,那么默认情况下会使用JDK的动态代理,如果目标没有实现接口会使用CGLIB,性能比较高,来进行代理,因为CGLIB是直接继承于被代理类,但是如果是final修饰的类,就需要使用CGLIB了;
1.2)之前前端都是直接访问后端的,但是现在前端是直接访问代理对象,不在直接访问后端,动态代理,是动态生成的,是在运行期生成的;
2)但是如果说被代理类是由final修饰的,那么我们只能使用JDK的动态代理(性能比较低);
3)咱们的@Transaction在开始执行之前,先通过动态代理对象来进行开启事务,再执行成功之后再进行提交事务,如果说中途遇到异常,那么我们就进行回滚事务,在底层通过代理使用TransactionManger来进行开启事务,执行咱们的业务逻辑(执行目标对象的方法),目标代理对象再来调用TransactionManger用来提交事务或者回滚事务;
3)这个时候是通过环绕通知的方法来进行执行事务操作的,把代理类放到Spring里面,切面是针对AOP哪些方面做的一个事情,当前是针对所有需要进行针对事务的操作做了一个拦截器,做了一个动态代理,是针对这个方面来做的,还是通过之前使用编程式事务来开启事务提交事务的;
1)上图从左向右开始执行,如果没有加@Transaction注解,那么就直接把原对象加入到IOC容器里面,用的时候还是使用的是原对象;
2)如果加上@Transaction作为切面就会创建一个代理对象放到IOC容器里面,每一次代理对象经过一系列操作之后才会进入到目标方法,使用的时候使用的是代理对象,代码在执行的时候,先经过代理对象的逻辑,代理对象的逻辑没有问题,再去执行目标方法;
3)加不加@Transaction最终放到IOC容器里面的东西是不一样的;
4)在IOC容器里不是原对象而是一个代理对象,先执行代理对象的一个开启事务,然后执行目标对象的一个方法,如果目标对象出现了BUG,就自动回滚事务,如果没有问题,就自动提交事务;
5)如果在类上或者方法上加上了@Trasaction表示所有加了public的方法都会创建事务;
调试前端代码:打开开发者工具,打开Sources里面就包含了里面全部的前端代码
找到里面的JS,对JS打点,运行项目,就可以自动执行到打点的位置,况且可以在Watch里面输入我们要进行搜寻的变量,点击确认就可以查看我们能够查看的值了
1)什么情况下会导致@Transaction失效?
Spring事物的隔离级别是建立在连接的数据库支持事务的基础上的,Spring项目最终进行连接的数据库不支持事务,那么即使在Spring设置了事务隔离级别,那么也是不会生效的
@Autowired TransactionTemplate template; template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
1)非public字段修饰的方法
2)timeOut指定时间超时,这种情况就是我们在@Transaction上进行设置了一个比较小的超时时间,如果说最终执行方法的超时时间超过了咱们timeout所进行指定的时间,那么最终会导致事务自动进行回滚操作
3)代码中出现了try catch但是没有手动抛出异常或者执行语句块处理操作
4)调用类内部的@Transaction方法:
@Controller public class UserController { @Autowired UserMapper userMapper; @RequestMapping("/Java100") public void start(User user) throws InterruptedException { run(user); } @Transactional public int run(User user) throws InterruptedException { int result= userMapper.Insert(user); int num=10/0; return result; }
5)数据库本身就不支持事务操作:我们的程序中向掉用的数据库里面发送了开启事务,提交事务,回滚事务的指令,但是如果说数据库本身就不支持事务,比如说MYSQL中进行设置了MYSIM存储引擎,这个存储引擎本身就是不会支持事务的,这种情况下,即使程序中添加了@Transaction注解,那么也是不会有事务的行为
2)但是为什么Spring项目中的@Transaction会失效呢?
1)在非public修饰的方法上面,即使加上了@Transaction也不会失效,原因是Transaction是通过SpringAOP来进行实现的,而SpringAOP是通过动态代理来进行实现的,而咱们的@Transaction在进行生成代理的时候会进行自动判断,如果是非public进行修饰的方法,那么不会生成代理对象,这样也就没有办法继续执行事务操作了;
2)在咱们使用@Transaction使用原理是在方法执行前会自动开启事务,在方法执行成功之后,会自动提交事务,如果在方法执行期间出现了异常,那么就会自动进行回滚事务,如果在方法中加上了try catch语句块之后,事务就不会进行自动回滚了,执行的方法里面出现了异常,那么@Transaction才能进行感知到,但是当开发者加上了try/catch之后,@Transaction就无法感知到异常了,就不会自动触发到事务的回滚操作了,源码无法感知
在你自己的业务代码里面使用try catch进行包裹以后,底层的代理对象就无法通过try catch就无法通过try catch感知到了,所以try catch包裹异常类型事务无法进行回滚
4)当我们进行调用类内部的@Transaction方法的时候,即使程序中出现了异常,那么也不会进行回滚,那是因为咱们的@Transaction是基于SpringAOP来进行实现的,而SpringAOP又是基于动态代理来进行实现的,而我们进行调用类内部的方法的时候,不是通过代理对象来进行完成的,而是通过this对象来进行实现这样就绕过了代理对象,那么事务就会失效了
3)SpringBoot事务不进行回滚怎么办呢?
1)调用类内部的方法的时候,给两个方法都加上@Transaction注解就可以了;
2)try catch之前说了;
3)加上public方法;
4)修改数据库默认的存储引擎为InnoDB;
5)@Transaction默认是只进行回滚运行时异常RunTimeException和error,而不会进行回滚受查异常编译时异常,我们的解决方案就是给@Transaction加上一个参数是rollback for exception.class
项目解决问题:检查缓存+百度
1)打开浏览器的开发者工具抓包,定位问题,前端问题或者是后端问题;
2)如果是前端问题使用开发者工具进行打点调试,打开开发者工具,点击source就可以得到所有的html,css,和js代码,JS出问题,在JS打点运行,如果初次打开开发者工具,代码啥也没有,就在刷新一遍,点击代码里面的变量或者是watch观察变量的值;
3)如果是后端问题使用IDEA工具Debug一步一步发现问题进行调试,看日志,反思和思考,实在不行删除target文件或者是删除jar包,重新运行;