下面是核心处理方法,把不太重要的代码忽略了,留下每一步的节点 。
@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // 获取事务属性 final TransactionManager tm = determ.NETransactionManager(txAttr); // 准备事务 TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status); // 执行目标方法 Object retVal = invocation.proceedWithInvocation();// 回滚事务 completeTransactionAfterThrowing(txInfo, ex); // 提交事务 commitTransactionAfterReturning(txInfo);}四、五种失效和解决方案

文章插图
下面我们从几个情况来给大家展示失效场景并给出解决方案 。
1、类没有被 Spring 管理
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Override@Transactional(rollbackFor = Exception.class)public void addUser(User user) {userDao.addUser(user);}}如上代码所示,UserServiceImpl 类没有被声明为 Spring Bean,因此其中的 addUser() 方法无法受到 Spring 事务管理的保护 。我们使用Spring,要把类交给Spring进行管理,不然是无法生效!「解决方案:」 交给spring进行管理bean,在类上添加:@Service!
2、方法不是public修饰
@Servicepublic class UserService {@Autowiredprivate UserDao userDao;@Transactional(rollbackFor = Exception.class)protected void addUser(User user) {userDao.addUser(user);}}我们上面说了声明式事务是基于AOP实现的,AOP是通过代理模式实现的,即为目标对象生成一个代理对象,当调用代理对象的方法时,会自动添加事务的控制代码 。在这种情况下,如果事务注释所在的方法不是public的,则无法生成代理对象,因此事务代码将无法添加到方法执行前后,导致事务失效 。其实这种情况还是不经常这么使用,我们基本都是使用接口和实现大部分都是public修饰的!
「解决方案:」 使用public来修饰方法 。
3、异常被捕获并处理了
@Servicepublic class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Override@Transactional(rollbackFor = Exception.class)public void addUser(User user) {try {userDao.addUser(user);} catch (Exception e) {// 处理异常,但没有抛出或重新抛出异常log.error("add user error", e);}}}如上代码所示,如果 userDao.addUser() 方法抛出异常,但是在 UserServiceImpl.addUser() 中被捕获并处理了,事务检测不到有异常抛出,那么事务不会回滚 。「解决方案:」 catch 处理完成后,在重新把异常在抛出去:throw e 。
4、同一个类中,方法内部调用
@Servicepublic class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic void addUser(User user) {doAddUser(user);}@Transactional(rollbackFor = Exception.class)public void doAddUser(User user) {userDao.addUser(user);}}Spring使用代理来实现事务控制,但是这种方法直接调用了this对象的方法,则无法通过代理来拦截该方法调用,从而使得事务失效 。「解决方案:」推荐使用有两种:
- 使用ApplicationContext来获取当前bean对象来调用doAddUser方法 。
- 在addUser方法加上@Transactional(rollbackFor = Exception.class) 。
方法一完整展示:
如果觉得Service里注入ApplicationContext 不优雅,可以抽到单独的工具bean里!
@Servicepublic class UserServiceImpl implements UserService { @Autowiredprivate UserDao userDao;@Autowired private ApplicationContext applicationContext;@Overridepublic void addUser(User user) {UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);userService.doAddUser(user);}@Transactional(rollbackFor = Exception.class)public void doAddUser(User user) {userDao.addUser(user);}}5、MySQL存储引警不支持事务MyISAM 存储引擎是 MySQL 的一种存储引擎,它是 MySQL 5.1 版本之前的默认存储引擎,它是不支持事务的 。从 MySQL 5.5 版本开始,InnoDB 成为了 MySQL 的默认存储引擎 。我们想使用也可以切换到MyISAM引擎 。「解决方案:」 把mysql换到5.5以上使用InnoDB 存储引擎 。
「补充使用MyISAM 方式:」
- 表从 InnoDB 引擎转换为 MyISAM 引擎:使用 ALTER TABLE 命令来更改表的引擎类型 。
推荐阅读
- Spring-retry详解
- SpringBoot自动装配原理
- Spring MVC工作流程,你学会了吗?
- SpringIOC循环依赖问题
- Spring Cloud Gateway路由元信息作用及路由超时配置详解
- Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战
- 军人|建议退役军人事务部门全部招聘军转干部,更好服务退役军人
- 霍启刚|霍家再次陷争产风波,霍启刚头脑清醒,陪特首出访不理会家族事务
- 酵母放久了会失效吗
- SpringBoot+Vue+ES 实现仿百度全文搜索
