h5素材做多的网站,如何做流量网站,邢台哪里做网站,企业管理系统一、背景 在 springBoot 开发过程中#xff0c;我们一般都是在业务方法上添加 Transactional 注解来让 spring 替我们管理事务#xff0c;但在某些特定的场景下#xff0c;添加完注解之后#xff0c;事务是不生效的#xff0c;接下来详细介绍下。
二、方法不是 public
2…一、背景 在 springBoot 开发过程中我们一般都是在业务方法上添加 Transactional 注解来让 spring 替我们管理事务但在某些特定的场景下添加完注解之后事务是不生效的接下来详细介绍下。
二、方法不是 public
2.1 场景描述 当添加 Transactional 注解的方法不是 public 类型的事务会失效。如下代码
Transactional
private void someTransactionalMethod() {// 业务逻辑
}
2.2 原因分析 在 Spring 中只有 public 方法才能被 AOP 代理处理因此如果 Transactional 注解的方法不是 public 的事务管理将失效。
2.3 解决方案 确保 Transactional 注解的方法是 public。如下
Transactional
public void someTransactionalMethod() {// 业务逻辑
}
三、方法内部调用
3.1 场景描述 当一个类内部的方法调用另一个标注了 Transactional 的方法时事务管理将失效。如下代码
Service
public class MyServiceImpl {public void outerMethod() {publicMethod();} Transactionalpublic void publicMethod() {// 业务逻辑}
}3.2 原因分析 这是因为内部方法方法的调用没有经过代理类即在 outerMethod() 方法里面调用的 publicMethod() 方法是 MyServiceImpl 对象调用的并不是经过 spring 代理类来调用的所以事务会失效。
3.3 解决方案 解决方案就是通过代理对象方法调用使用 AOP 代理进行事务管理如下代码
Service
public class MyServiceImpl {public void outerMethod() {// 通过代理对象调用 publicMethod((MyServiceImpl) AopContext.currentProxy()).publicMethod();} Transactionalpublic void publicMethod() {// 业务逻辑}
}
四、未被 spring 管理
4.1 场景描述 当一个类没有被 spring 管理时事务不会生效如下代码
public class MyServiceImpl {Transactionalpublic void someTransactionalMethod() {// 业务逻辑}
}
4.2 原因分析 只有在 Spring 容器中管理的 bean才能被 AOP 代理。如果 Transactional 注解的方法所在的类没有被 Spring 管理事务管理将失效。
4.3 解决方案 确保类被 Spring 容器管理如通过 ServiceComponent 等注解。
Service
public class MyServiceImpl {Transactionalpublic void someTransactionalMethod() {// 业务逻辑}
}
五、方法用 final 或 static 修饰
5.1 场景描述 有时候某个方法不想被子类重写这时可以将该方法定义成 final 的。普通方法这样定义是没问题的但如果将事务方法定义成 final那么事务将会失效。
Service
public class UserService {Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
5.2 原因分析 spring 事务底层使用了 aop也就是通过 jdk 动态代理或者 cglib帮我们生成了代理类在代理类中实现的事务功能。但如果某个方法用 final 修饰了那么在它的代理类中就无法重写该方法而添加事务功能。 注意如果某个方法是 static 的同样无法通过动态代理变成事务方法。
5.3 解决方案 不使用 final 或者 static 修饰方法如下
Service
public class UserService {Transactionalpublic void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
六、配置不当
6.1 场景描述 Transactional 注解的一些配置属性可能会影响事务的行为如下代码
Transactional(readOnly true)
public void someTransactionalMethod() {// 业务逻辑
}
6.2 原因分析 配置了 readOnlytrue 属性那么执行增删改操作时就会报错。因为这个属性指定了此方法只能进行读操作。
6.3 解决方案 检查配置的具体含义确保其适当应用。
Transactional(readOnly false)
public void someTransactionalMethod() {// 业务逻辑
}
七、多线程调用
7.1 场景描述 spring 事务在多线程场景下会有问题如下代码
Service
public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() - {roleService.doOtherThing();}).start();}
}Service
public class RoleService {Transactionalpublic void doOtherThing() {System.out.println(保存role表数据);}
}7.2 原因分析 从上面的例子中我们可以看到事务方法 add 中调用了事务方法 doOtherThing但是事务方法 doOtherThing 是在另外一个线程中调用的。 这样会导致两个方法不在同一个线程中获取到的数据库连接不一样从而是两个不同的事务。如果将来 doOtherThing 方法中抛了异常add 方法也回滚是不可能的。 如果看过 spring 事务源码的朋友可能会知道 spring 的事务是通过数据库连接来实现的。当前线程中保存了一个 mapkey 是数据源value 是数据库连接。 我们说的同一个事务其实是指同一个数据库连接只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程拿到的数据库连接肯定是不一样的所以是不同的事务。
7.3 解决方案 避免在多线程中使用 Transactional或者手动管理线程间的事务。
Service
public class MyService {Transactionalpublic void someTransactionalMethod() {ExecutorService executorService Executors.newSingleThreadExecutor();executorService.submit(() - {// 手动管理事务TransactionStatus status transactionManager.getTransaction(new DefaultTransactionDefinition());try {// 业务逻辑transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);throw e;}});}
}
八、错误的传播特性
8.1 场景描述 如果我们在手动设置 propagation 参数的时候把传播特性设置错了事务可能就不会生效如下代码
Service
public class UserService {Transactional(propagation Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
8.2 原因分析 propagation 参数的作用是指定事务的传播特性spring 目前支持 7 种传播特性 EQUIRED如果当前上下文中存在事务则加入该事务如果不存在事务则创建一个事务这是默认的传播属性值。 SUPPORTS如果当前上下文中存在事务则支持事务加入事务如果不存在事务则使用非事务的方式执行。 MANDATORY当前上下文中必须存在事务否则抛出异常。 REQUIRES_NEW每次都会新建一个事务并且同时将上下文中的事务挂起执行当前新建事务完成以后上下文事务恢复再执行。 NOT_SUPPORTED如果当前上下文中存在事务则挂起当前事务然后新的方法在没有事务的环境中执行。 NEVER如果当前上下文中存在事务则抛出异常否则在无事务环境上执行代码。 NESTED如果当前上下文中存在事务则嵌套事务执行如果不存在事务则新建事务。 我们可以看到 add 方法的事务传播特性定义成了 Propagation.NEVER这种类型的传播特性不支持事务如果有事务则会抛异常。
8.3 解决方案 目前只有这三种传播特性才会创建新事务REQUIREDREQUIRES_NEWNESTED。
Service
public class UserService {Transactional(propagation Propagation.REQUIRED)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
九、自己吞了异常
8.1 场景描述 开发者在代码中手动 try...catch 了异常事务不会生效如下代码
Slf4j
Service
public class UserService {Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}}
}
8.2 原因分析 这种情况下 spring 事务当然不会回滚因为开发者自己捕获了异常又没有手动抛出换句话说就是把异常吞掉了。
8.3 解决方案 如果想要 spring 事务能够正常回滚必须抛出它能够处理的异常。如果没有抛异常则 spring 认为程序是正常的。如下代码
Slf4j
Service
public class UserService {Transactionalpublic void add(UserModel userModel)throws Exception {saveData(userModel);updateData(userModel);}
}
十、手动抛了别的异常
8.1 场景描述 即使开发者没有手动捕获异常但如果抛的异常不正确spring 事务也不会回滚。如下代码
Slf4j
Service
public class UserService {Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}}
}
8.2 原因分析 上面的这种情况开发人员自己捕获了异常又手动抛出了异常Exception事务同样不会回滚。 因为 spring 事务默认情况下只会回滚 RuntimeException运行时异常和 Error错误对于普通的 Exception非运行时异常它不会回滚。
8.3 解决方案 别采取这种写法。
十一、自定义了回滚异常
11.1 场景描述 在使用 Transactional 注解声明事务时有时我们想自定义回滚的异常spring 也是支持的。可以通过设置 rollbackFor 参数来完成这个功能。但如果这个参数的值设置错了就会引出一些莫名其妙的问题如下代码
Slf4j
Service
public class UserService {Transactional(rollbackFor BusinessException.class)public void add(UserModel userModel) throws Exception {saveData(userModel);updateData(userModel);}
}
11.2 原因分析 如果在执行上面这段代码保存和更新数据时程序报错了抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常报错的异常不属于 BusinessException所以事务也不会回滚。 即使 rollbackFor 有默认值但阿里巴巴开发者规范中还是要求开发者重新指定该参数。
11.3 解决方案 如果使用默认值一旦程序抛出了 Exception事务不会回滚这会出现很大的 bug。所以建议一般情况下将该参数设置成Exception 或 Throwable。