一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述完蛋,我的事务怎么不生效?相关的知识,希望能为你提供帮助。
前言事务大家平时应该都有写,之前写事务的时候遇到一点坑,居然不生效,后来排查了一下,复习了一下各种事务失效的场景,想着不如来一个总结,这样下次排查问题,就能有恃无恐了。那么先来复习一下事务相关知识,事务是指操作的最小工作单位,作为一个单独且不可切割的单元操作,要么全部成功,要么全部失败。事务有四大特性(??ACID?
?):
- 原子性(?
?Atomicity?
?):事务包含的操作,要么全部成功,要么全部失败回滚,不会存在一半成功一半失败的中间状态。比如??A?
?和??B?
?一开始都有??500?
?元,??A?
?给??B?
?转账??100?
?,那么??A?
?的钱少了??100?
?,??B?
?的钱就必须多了??100?
?,不能??A?
?少了钱,??B?
?也没收到钱,那这个钱就不翼而飞了,不符合原子性了。 - 一致性(?
?Consistency?
?):一致性是指事务执行之前和之后,保持整体状态的一致,比如??A?
?和??B?
?一开始都有??500?
?元,加起来是??1000?
?元,这个是之前的状态,??A?
?给??B?
?转账??100?
?,那么最后??A?
?是??400?
?,??B?
?是??600?
?,两者加起来还是??1000?
?,这个整体状态需要保证。 - 隔离性(?
?Isolation?
?):前面两个特性都是针对同一个事务的,而隔离性指的是不同的事务,当多个事务同时在操作同一个数据的时候,需要隔离不同事务之间的影响,并发执行的事务之间不能相互干扰。 - 持久性(?
?Durability?
?):指事务如果一旦被提交了,那么对数据库的修改就是永久性的,就算是数据库发生故障了,已经发生的修改也必然存在。
事务不仅是适用于数据库。我们可以将此概念扩展到其他组件,类似队列服务或外部系统状态。因此,“一系列数据操作语句必须完全完成或完全失败,以一致的状态离开系统”测试环境
前面我们已经部署过了一些demo项目,以及用docker快速搭建环境,本文基于的也是之前的环境:
- JDK 1.8
- Maven 3.6
- Docker
- mysql
正常的事务样例,包含两个接口,一个是获取所有的用户中的数据,另外一个更新的,是?
?update?
?用户数据,其实就是每个用户的年龄??+1?
?,我们让一次操作完第一个之后,抛出异常,看看最后的结果:@Service("userService")
public class UserServiceImpl implements UserService
@Resource
UserMapper userMapper;
@Autowired
RedisUtil redisUtil;
@Override
public List< User> getAllUsers()
List< User> users = userMapper.getAllUsers();
return users;
@Override
@Transactional
public void updateUserAge()
userMapper.updateUserAge(1);
int i= 1/0;
userMapper.updateUserAge(2);
数据库操作:
< ?xml version="1.0" encoding="UTF-8" ?>
< !DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace="com.aphysia.springdocker.mapper.UserMapper">
< select id="getAllUsers" resultType="com.aphysia.springdocker.model.User">
SELECT * FROM user
< /select>
< update id="updateUserAge" parameterType="java.lang.Integer">
update user set age=age+1 where id =#id
< /update>
< /mapper>
先获取?
?http://localhost:8081/getUserList?
?所有的用户看看:在调用更新接口,页面抛出错误了:
控制台也出现了异常,意思是除以0,异常:
java.lang.ArithmeticException: / by zero
at com.aphysia.springdocker.service.impl.UserServiceImpl.updateUserAge(UserServiceImpl.java:35) < sub> [classes/:na]
at com.aphysia.springdocker.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$c8cc4526.invoke(< generated> ) < /sub> [classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) < sub> [spring-core-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) < /sub> [spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) < sub> [spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) < /sub> [spring-aop-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) < sub> [spring-tx-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) < /sub> [spring-tx-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) < sub> [spring-tx-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) < /sub> [spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) < sub> [spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) < /sub> [spring-aop-5.3.12.jar:5.3.12]
at com.aphysia.springdocker.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$25070cf0.updateUserAge(< generated> ) ~[classes/:na]
然后我们再次请求?
?http://localhost:8081/getUserList?
?,看到数据两个都是??11?
?说明数据都没有发生变化,第一个操作完之后,异常,回滚成功了:["id":1,"name":"李四","age":11,"id":2,"name":"王五","age":11]
那什么时候事务不正常回滚呢?且听我细细道来:
实验1. 引擎设置不对
我们知道,?
?Mysql?
?其实有一个数据库引擎的概念,我们可以用??show engines?
?来查看??Mysql?
?支持的数据引擎:可以看到?
?Transactions?
?那一列,也就是事务支持,只有??InnoDB?
?,那就是只有??InnoDB?
?支持事务,所以要是引擎设置成其他的事务会无效。我们可以用??show variables like default_storage_engine?
?看默认的数据库引擎,可以看到默认是??InnoDB?
?:mysql> show variables like default_storage_engine;
+------------------------+--------+
| Variable_name| Value|
+------------------------+--------+
| default_storage_engine | InnoDB |
+------------------------+--------+
那我们看看我们演示的数据表是不是也是用了?
?InnoDB?
?,可以看到确实是使用??InnoDB?
?那我们把该表的引擎修改成?
?MyISAM?
?会怎么样呢?试试,在这里我们只修改数据表的数据引擎:mysql> ALTER TABLE user ENGINE=MyISAM;
Query OK, 2 rows affected (0.06 sec)
Records: 2Duplicates: 0Warnings: 0
然后再?
?update?
?,不出意料,还是会报错,看起来错误没有什么不同:但是获取全部数据的时候,第一个数据更新成功了,第二个数据没有更新成功,说明事务没有生效。
["id":1,"name":"李四","age":12,"id":2,"name":"王五","age":11]
结论:必须设置为?
?InnoDB?
?引擎,事务才生效。2. 方法不能是 private
事务必须是?
?public?
?方法,如果用在了??private?
?方法上,那么事务会自动失效,但是在??IDEA?
?中,只要我们写了就会报错:??Methods annotated with @Transactional must be overrideable?
?,意思是事务的注解加上的方法,必须是可以重写的,??private?
?方法是不可以重写的,所以报错了。同样的?
?final?
?修饰的方法,如果加上了注解,也会报错,因为用??final?
?就是不想被重写:?
?Spring?
?中主要是用放射获取??Bean?
?的注解信息,然后利用基于动态代理技术的??AOP?
?来封装了整个事务,理论上我想调用??private?
?方法也是没有问题的,在方法级别使用??method.setAccessible(true);
?
?就可以,但是可能??Spring?
?团队觉得??private?
?方法就是开发人员意愿上不愿意公开的接口,没有必要破坏封装性,这样容易导致混乱。??Protected?
?方法可不可以?不可以!下面我们为了实现,魔改代码结构,因为接口不能用??Portected?
?,如果用了接口,就不可能用??protected?
?方法,会直接报错,而且必须在同一个包里面使用,我们把??controller?
?和??service?
?放到同一个包下:测试后发现事务不生效,结果依然是一个更新了,另外一个没有更新:
["id":1,"name":"李四","age":12,"id":2,"name":"王五","age":11]
结论:必须使用在?
?public?
?方法上,不能用在??private?
?,??final?
?,??static?
?方法上,否则不会生效。3. 异常必须是运行期的异常
?
?Springboot?
?管理异常的时候,只会对运行时的异常(??RuntimeException?
? 以及它的子类) 进行回滚,比如我们前面写的??i=1/0;
?
?,就会产生运行时的异常。从源码来看也可以看到,?【完蛋,我的事务怎么不生效()】
?rollbackOn(ex)?
?方法会判断异常是?
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #私藏项目实操分享#微信小程序开发(集成腾讯地图的步骤)
- K8S部署05--node部署
- WordPress主题开发快速入门
- 主题面板中的WordPress logo上传选项
- 在wordpress根主题中包含React js的正确方法
- 如何将WordPress模板与CodeIgniter集成
- 如何在WordPress主题中包含jQuery()
- get_header操作不起作用
- 通过管理员在WordPress中编辑页脚内容