好的网站建设启示,全国最好的广告公司加盟,企业网站后台,做爰网站1000部在Spring框架中#xff0c;Transactional注解用于声明式事务管理#xff0c;能够简化事务的处理逻辑。然而#xff0c;在某些情况下#xff0c;Transactional可能会失效#xff0c;导致事务无法按预期工作。了解这些失效场景及其原因#xff0c;可以帮助你更好地管理和调…在Spring框架中Transactional注解用于声明式事务管理能够简化事务的处理逻辑。然而在某些情况下Transactional可能会失效导致事务无法按预期工作。了解这些失效场景及其原因可以帮助你更好地管理和调试事务问题。
1、Transactional失效的常见场景
1、方法非public访问权限
Transactional注解通常只能应用于public方法上。如果将其应用于protected、private或包级私有方法上由于Spring的代理机制无法拦截这些方法的调用因此事务注解将失效。
2、同一个类的内部调用
当Transactional注解的方法在同一类内部被另一个方法调用时事务可能会失效。这是因为Spring的AOP面向切面编程机制是通过代理对象来实现事务管理的。只有当外部类通过代理对象调用带有Transactional注解的方法时Spring才会拦截该方法并为其创建事务。而在同一类内部直接调用方法时不会经过代理对象因此事务不会生效。
内部调用示例
Service
public class UserService {Autowiredprivate UserRepository userRepository;// 带有Transactional注解的方法Transactionalpublic void createUser(User user) {userRepository.save(user);}// 同一类内部调用带有Transactional注解的方法public void createUserInternal() {User user new User();user.setName(Alice);createUser(user); // 事务不会生效}
}解释 在上面的例子中createUser方法虽然带有Transactional注解但在createUserInternal方法中直接调用了createUser这不会触发Spring的事务管理机制。因为createUserInternal和createUser是同一个类的方法调用是通过this引用进行的而不是通过代理对象调用的因此事务不会生效。
解决方案
拆分到不同类将createUser方法移到另一个服务类中确保它是通过代理对象调用的。 示例 Servicepublic class UserService {Autowiredprivate UserRepository userRepository;Transactionalpublic void createUser(User user) {userRepository.save(user);}}Servicepublic class AnotherService {Autowiredprivate UserService userService;public void createUserInternal() {User user new User();user.setName(Alice);userService.createUser(user); // 通过代理对象调用事务生效}}使用自定义代理可以通过AopContext.currentProxy()获取当前类的代理对象然后通过代理对象调用方法。 示例 Servicepublic class UserService {Autowiredprivate UserRepository userRepository;Transactionalpublic void createUser(User user) {userRepository.save(user);}public void createUserInternal() {User user new User();user.setName(Alice);((UserService) AopContext.currentProxy()).createUser(user); // 使用代理对象调用}}3、事务管理器配置错误
如果Spring容器中配置了多个事务管理器但在使用Transactional注解时没有明确指定事务管理器可能会导致Spring使用默认的事务管理器而这个默认的事务管理器可能不适用于当前的操作从而导致事务注解失效。
4、方法内部捕捉异常
在使用Transactional注解的方法中如果内部捕获了可能导致事务回滚的异常并且没有重新抛出一个Spring框架能够识别的运行时异常或声明式异常那么事务管理器将无法感知到异常从而可能导致事务不会回滚。 Transactional注解默认只会对RuntimeException和未检查的异常进行回滚。如果在事务方法中捕获了异常并吞掉即没有抛出或记录事务将不会回滚导致数据不一致。
错误示例
Service
public class UserService {Autowiredprivate UserRepository userRepository;Transactionalpublic void createUser(User user) {try {userRepository.save(user);// 模拟异常if (user.getName().equals(Alice)) {throw new RuntimeException(模拟异常);}} catch (Exception e) {// 异常被捕获并吞掉事务不会回滚System.out.println(捕获到异常 e.getMessage());}}
}解释 在上面的例子中当user.getName()等于Alice时会抛出一个RuntimeException但这个异常被捕获并在catch块中处理没有重新抛出。由于Transactional注解默认只对未捕获的RuntimeException进行回滚因此事务不会回滚导致数据不一致。
解决方案
不要吞掉异常确保在catch块中记录异常日志并根据需要重新抛出异常。 示例 Transactionalpublic void createUser(User user) {try {userRepository.save(user);if (user.getName().equals(Alice)) {throw new RuntimeException(模拟异常);}} catch (Exception e) {// 记录异常日志logger.error(创建用户时发生异常, e);// 重新抛出异常确保事务回滚throw e;}}手动标记事务为回滚如果不希望重新抛出异常可以使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记事务为回滚。 示例 import org.springframework.transaction.interceptor.TransactionAspectSupport;Transactionalpublic void createUser(User user) {try {userRepository.save(user);if (user.getName().equals(Alice)) {throw new RuntimeException(模拟异常);}} catch (Exception e) {// 手动标记事务为回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();logger.error(创建用户时发生异常, e);}}5、使用final修饰的方法
如果使用final关键字修饰了方法那么由于该方法不能被重写Spring的代理机制将无法对其应用Transactional注解因此事务将失效。
6、静态方法
静态方法同样无法通过动态代理来应用Transactional注解因为静态方法不属于类的实例方法而是属于类本身。
7、未被Spring管理的类
如果一个类没有被Spring管理即没有使用Controller、Service、Component、Repository等注解进行标注那么该类中的方法即使使用了Transactional注解也不会生效。
8、传播特性配置错误
Transactional 注解支持多种事务传播行为Propagation不同的传播行为会影响事务的创建和管理方式。如果不正确配置传播行为可能会导致事务不按预期工作。 可以指定propagation参数来定义事务的传播行为。如果传播特性配置错误例如设置为Propagation.NEVER而当前存在事务则事务将不会生效。
常见的传播行为
REQUIRED默认如果当前存在事务则加入该事务否则创建一个新的事务。REQUIRES_NEW总是创建一个新的事务如果当前存在事务则将其挂起。SUPPORTS如果当前存在事务则加入该事务否则以非事务方式执行。NOT_SUPPORTED以非事务方式执行如果当前存在事务则将其挂起。MANDATORY必须在一个现有的事务中执行否则抛出异常。NEVER必须在没有事务的情况下执行否则抛出异常。NESTED如果当前存在事务则在嵌套事务内执行否则创建一个新的事务。
示例
Service
public class UserService {Transactional(propagation Propagation.REQUIRED)public void methodA() {// 执行一些数据库操作methodB();}Transactional(propagation Propagation.NOT_SUPPORTED)public void methodB() {// 这里的事务不会生效因为propagation设置为NOT_SUPPORTED// 任何数据库操作都不会参与事务}
}解决方案
根据业务需求选择合适的传播行为。例如如果你希望methodB与methodA共享同一个事务应该使用REQUIRED或REQUIRES_NEW而不是NOT_SUPPORTED或NEVER。 改进后的代码 Servicepublic class UserService {Transactional(propagation Propagation.REQUIRED)public void methodA() {// 执行一些数据库操作methodB();}Transactional(propagation Propagation.REQUIRED)public void methodB() {// 现在 methodB 会与 methodA 共享同一个事务}}9、数据库不支持事务
如果使用的数据库表不支持事务例如某些类型的存储引擎或数据库系统不支持事务那么即使使用了Transactional注解事务也不会生效。
10、异步线程调用
在Spring中Async注解用于开启异步任务执行。默认情况下Async和Transactional不能同时生效。这是因为在异步任务中Spring的事务管理器无法正确管理事务导致事务失效。
异常示例
Service
public class AsyncService {Autowiredprivate UserRepository userRepository;AsyncTransactionalpublic void createUserAsync(User user) {userRepository.save(user);// 模拟异常if (user.getName().equals(Alice)) {throw new RuntimeException(模拟异常);}}
}解释 在上面的例子中createUserAsync方法同时标注了Async和Transactional。由于Async注解会将方法的执行交给一个新的线程池而Spring的事务管理器是基于主线程的因此在异步任务中事务管理器无法正确管理事务导致事务失效。
解决方案
使用Propagation.REQUIRES_NEW可以在异步方法中使用Propagation.REQUIRES_NEW来强制创建一个新的事务。这样即使在异步任务中事务也能正常工作。 示例 AsyncTransactional(propagation Propagation.REQUIRES_NEW) // 强制执行事务public void createUserAsync(User user) {userRepository.save(user);if (user.getName().equals(Alice)) {throw new RuntimeException(模拟异常);}}避免在异步方法中使用事务如果异步任务不需要事务支持建议将事务逻辑移到同步方法中或者在异步任务中手动管理事务。
11、事务回滚规则设置不当
Transactional注解允许你指定哪些异常会导致事务回滚rollbackFor和哪些异常不会导致事务回滚noRollbackFor。如果不正确配置这些规则可能会导致事务在不应该回滚的情况下回滚或者在应该回滚的情况下没有回滚。
示例
Service
public class UserService {Transactional(rollbackFor Exception.class)public void updateUser(User user) {// 执行更新操作userMapper.updateUser(user);// 抛出一个自定义异常throw new CustomException(自定义异常);}public class CustomException extends Exception {public CustomException(String message) {super(message);}}
}解决方案
根据业务需求合理配置rollbackFor和noRollbackFor。通常情况下Transactional默认只会对RuntimeException和其子类进行回滚。如果你希望对其他类型的异常也进行回滚可以使用rollbackFor指定具体的异常类型。 改进后的代码 Servicepublic class UserService {Transactional(rollbackFor {CustomException.class, RuntimeException.class})public void updateUser(User user) {// 执行更新操作userMapper.updateUser(user);// 抛出一个自定义异常throw new CustomException(自定义异常);}public class CustomException extends Exception {public CustomException(String message) {super(message);}}}如果你不希望某些异常导致事务回滚可以使用 noRollbackFor Servicepublic class UserService {Transactional(noRollbackFor CustomException.class)public void updateUser(User user) {// 执行更新操作userMapper.updateUser(user);// 抛出一个自定义异常throw new CustomException(自定义异常);}public class CustomException extends Exception {public CustomException(String message) {super(message);}}}2、避免Transactional注解失效策略
1、确保方法是public的。 2、避免在同一个类中直接调用其他事务方法可以通过注入自身的方式来解决。 3、正确配置事务管理器。 4、不要在事务方法内部捕获并处理可能导致事务回滚的异常或者重新抛出一个Spring框架能够识别的异常。 5、避免使用final和static修饰事务方法。 6、确保类被Spring管理。 7、正确配置事务的传播特性。 8、使用支持事务的数据库表和存储引擎。 9、在多线程环境下确保事务方法在同一个线程中执行。
通过遵循这些原则可以最大程度地确保Transactional注解在Spring框架中的正确性和有效性。
3、Transactional实现原理
在Spring框架中Transactional注解用于声明式事务管理它通过AOP面向切面编程来实现。Spring使用代理机制来拦截带有Transactional注解的方法调用并在其周围添加事务管理逻辑。
1、Spring创建代理对象
当Spring容器启动时它会扫描所有容器中带有Transactional 注解的方法并为这些方法创建一个代理对象。这个代理对象会拦截对原始业务逻辑类的调用并在方法执行前后添加事务管理逻辑。
这个代理对象会在方法调用前后执行以下操作
开启事务在方法执行之前Spring会检查当前是否存在事务。如果不存在则创建一个新的事务。提交或回滚事务在方法执行完毕后Spring会根据方法的执行结果决定是提交还是回滚事务。如果方法正常结束则提交事务如果方法抛出异常则根据配置决定是否回滚事务。传播行为Transactional注解还支持多种传播行为如REQUIRED、REQUIRES_NEW等决定了如何处理现有事务或创建新事务。
创建代理对象的方式
CGLIB动态代理默认方式 如果目标类没有实现接口Spring会使用CGLIB动态代理来生成代理类。CGLIB通过继承目标类并重写其方法来实现代理。JDK动态代理 如果目标类实现了接口Spring会优先使用JDK动态代理。JDK动态代理通过实现接口并使用InvocationHandler来拦截方法调用。
2、Transactional伪代码
下面是 Transactional 代理机制的伪代码描述展示了 Spring 如何通过代理对象来管理事务的生命周期。
示例
// 假设这是 Spring 创建的代理对象
public class UserServiceProxy implements InvocationHandler {// 目标对象原始的 UserService 实例private final Object target;// 事务管理器private final PlatformTransactionManager transactionManager;// 事务属性从 Transactional 注解中读取private final TransactionAttribute transactionAttribute;public UserServiceProxy(Object target, PlatformTransactionManager transactionManager, TransactionAttribute transactionAttribute) {this.target target;this.transactionManager transactionManager;this.transactionAttribute transactionAttribute;}// 拦截对目标对象的方法调用Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 检查是否是需要事务管理的方法if (method.isAnnotationPresent(Transactional.class)) {return executeWithTransaction(method, args);} else {// 如果不是事务方法直接调用目标对象的方法return method.invoke(target, args);}}// 执行带有事务管理的方法private Object executeWithTransaction(Method method, Object[] args) throws Throwable {// 1. 获取当前事务状态TransactionStatus status null;try {// 2. 开启新事务或加入现有事务status transactionManager.getTransaction(transactionAttribute);// 3. 调用目标对象的业务逻辑方法Object result method.invoke(target, args);// 4. 提交事务transactionManager.commit(status);// 5. 返回业务方法的结果return result;} catch (Exception e) {// 6. 如果发生异常回滚事务if (status ! null) {transactionManager.rollback(status);}// 7. 抛出异常让调用者处理throw e;} finally {// 8. 清理资源如关闭连接等// 这一步通常由连接池自动处理}}
}// 假设这是 Spring 容器中的代理工厂
public class TransactionalProxyFactory {public Object createProxy(Object target, Class?[] interfaces, PlatformTransactionManager transactionManager, TransactionAttribute transactionAttribute) {// 使用 JDK 动态代理if (interfaces.length 0) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),interfaces,new UserServiceProxy(target, transactionManager, transactionAttribute));}// 使用 CGLIB 动态代理else {Enhancer enhancer new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(new UserServiceProxy(target, transactionManager, transactionAttribute));return enhancer.create();}}
}3、实例分析解释
通过使用自我注入Self-Injection方式来解释下
示例
Service
public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate UserService self; // 自我注入Transactionalpublic void createUser(User user) {// 业务逻辑插入用户数据userMapper.insertUser(user);// 通过代理对象调用 updateUser 方法self.updateUser(user);}Transactionalpublic void updateUser(User user) {// 业务逻辑更新用户数据userMapper.updateUser(user);}
}解释
Spring服务在创建容器时会自动扫描ServiceComponentTransactional等注解并在容器中创建实例对象。在使用的地方通过Autowired或Resource注解可以拿出该对象的代理对象使用注意此时拿出来的是代理对象而不是原始对象。针对Transactional注解spring内部通过aop的方式对注解方法做了围绕增强如帮我们开启事务结束帮我们提交事务等如第二部分的伪代码。在本例中本身就是UserService内部在注入UserService self对象这两个对象都是实现一样的功能只不过self对象通过Autowired注入实际为UserService的代理对象。 如果类中直接使用自身的updateUser方法属于内部直接调用的范畴。如果使用self对象调用updateUser方法则是通过代理对象实现的。代理对象会执行aop使事务生效。内部调用没有调用aop所以事务就不会起作用了。
乘风破浪会有时直挂云帆济沧海