如何提交网站给百度,wordpress字体抖动,wordpress远程图片本地化,国外浏览器入口前言 尽管 Transactional 注解在 Spring 中提供了方便的事务管理功能#xff0c;我们在使用过程中却常常面临其失效的问题。事务失效可能导致意想不到的数据状态和错误#xff0c;影响应用的稳定性和可靠性。本文将探讨一些常见的 Transactional 失效场景#xff0c;包括异常…前言 尽管 Transactional 注解在 Spring 中提供了方便的事务管理功能我们在使用过程中却常常面临其失效的问题。事务失效可能导致意想不到的数据状态和错误影响应用的稳定性和可靠性。本文将探讨一些常见的 Transactional 失效场景包括异常处理不当、事务传播行为设置错误以及在非 Spring 管理的类中使用注解等情况。通过深入分析这些场景我们将揭示如何避免这些常见陷阱以确保事务管理的有效性。 一、Transactional失效场景
1、代理角度
1.1同类内部方法间调用
非Tranactional注解方法调用Transactional注解方法
Transactional(rollbackFor Exception.class)
public void createUser(UserInfo user) throws UnknownHostException {userDao.save(user);
}public void createUser2(UserInfo user) throws UnknownHostException {createUser(user);
}当 createUser2 方法直接调用 createUser 方法时Spring 的 AOP 机制无法拦截这个方法调用。createUser上的Tranactional失效。
IDE也会给出相应警告 Tranactional注解方法调用Transactional注解方法
Transactional(rollbackFor Exception.class)
public void createUser(UserInfo user) throws UnknownHostException {userDao.save(user);
}Transactional(rollbackFor Exception.class)
public void createUser2(UserInfo user) throws UnknownHostException {createUser(user);
}当 createUser2 方法直接调用 createUser 方法时Spring 的 AOP 机制无法拦截这个方法调用。createUser上的Tranactional失效。
createUser中DML以非事务形式执行。
1.2如果Transactional标注到接口上
注解标注到接口上如果使用CGLIB对目标类进行代理将无法解析到Transactional
定义接口和实现类
public interface UserService {void createUser(UserInfo user);
}public class UserServiceImpl implements UserService {OverrideTransactional(rollbackFor Exception.class) // 注解在接口实现上public void createUser(UserInfo user) {// DML 操作System.out.println(User created: user.getUserId());}
}配置Spring和使用CGLIB代理
Configuration
EnableTransactionManagement
public class AppConfig {Beanpublic UserService userService() {return new UserServiceImpl(); // CGLIB 代理}Beanpublic DataSource dataSource() {// 配置数据源}
}测试类
public class TransactionTest {Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user new UserInfo();user.setUserId(1);userService.createUser(user);}
}在这个案例中UserService 接口的 createUser 方法被 Transactional 注解标记而 UserServiceImpl 实现类也是正确实现了这个接口。
由于 Spring 在这里使用 CGLIB 代理当代理目标类没有实现接口时默认使用 CGLIB它将创建一个 UserServiceImpl 的代理对象。
当通过接口调用 userService.createUser(user) 时代理对象并不会解析接口上的 Transactional 注解导致事务管理失效。
1.3final、static修饰的类或者方法上使用注解
1.3.1 final 修饰的类
无法被代理如果一个类被声明为 finalSpring 无法对其进行子类化因此无法生成代理。由于 Spring 的事务管理依赖于代理机制Transactional 注解将不会生效相关的事务管理功能将被忽略。
1.3.2static 修饰的方法
无法代理static 方法属于类本身而不是类的实例因此 Spring 事务管理也无法对其进行代理。这意味着即使在 static 方法上使用 Transactional事务管理也不会生效。
1.4没有被Spring管理的bean
Spring 的事务管理依赖于代理机制来处理方法调用。当你在一个类上使用 Transactional 注解时Spring 会创建一个代理对象并在调用被注解的方法时应用事务逻辑。如果一个 bean 没有被 Spring 管理例如它不是 Spring 上下文中的一个 beanSpring 就不会为它创建代理因此 Transactional 注解将不起作用。
2、框架及底层角度
2.1非public修饰的方法
/*** 创建代理时会把方法和事务属性放到缓存对象attributeCache中* 计算事务属性时会判断方式是否public修饰*/// AbstractFallbackTransactionAttributeSource
private final MapObject, TransactionAttribute attributeCache new ConcurrentHashMap(1024);protected TransactionAttribute computeTransactionAttribute(Method method, Nullable Class? targetClass) {// 不允许非public修饰的方法if (allowPublicMethodsOnly() !Modifier.isPublic(method.getModifiers())) {return null;}
}2.2多线程
Service
public class UserService {private final UserDao userDao;private final UserLoginService userLoginService;public UserService(UserDao userDao, UserLoginService userLoginService) {this.userDao userDao;this.userLoginService userLoginService;}Transactional(rollbackFor Exception.class)public void createUser(UserInfo user) {userDao.save(user);UserLoginInfo userLoginInfo new UserLoginInfo();userLoginInfo.setId(user.getUserId());userLoginInfo.setUserId(user.getUserId());userLoginService.saveUserLoginInfo(userLoginInfo);}
}Service
public class UserLoginService {private final UserLoginDao userLoginDao;public UserLoginService(UserLoginDao userLoginDao) {this.userLoginDao userLoginDao;}Transactional(rollbackFor Exception.class)Asyncpublic void saveUserLoginInfo(UserLoginInfo userLoginInfo) {userLoginDao.save(userLoginInfo);}
}理论上createUser和saveUserLoginInfo都有Transactional注解时两个方法中的DML处于同一个事务。
当在saveUserLoginInfo上加上Async时saveUserLoginInfo会被代理新开一个线程执行。
由于事务时绑定在线程上的这就意味着saveUserLoginInfo会新建一个事务。
protected TransactionInfo prepareTransactionInfo(Nullable PlatformTransactionManager tm,Nullable TransactionAttribute txAttr, String joinpointIdentification,Nullable TransactionStatus status) {TransactionInfo txInfo new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr ! null) {if (logger.isTraceEnabled()) {logger.trace(Getting transaction for [ txInfo.getJoinpointIdentification() ]);}txInfo.newTransactionStatus(status);} else {if (logger.isTraceEnabled()) {logger.trace(No need to create transaction for [ joinpointIdentification ]: This method is not transactional.);}}// 即使没有创建新的事务也总是绑定事务信息到线程上txInfo.bindToThread();return txInfo;
}2.3数据库本身不支持事务
MySQL如果使用MyISAM存储引擎则不支持事务自然Transactional也没有效果
2.4未开启事务
在某些MVC项目中需要在配置文件中手动配置事务相关参数
3、使用角度
3.1rollbackFor 属性设置错误而导致 Transactional 注解失效的情况
定义用户服务类
Service
public class UserService {Autowiredprivate UserDao userDao;Autowiredprivate UserLoginService userLoginService;Transactional(rollbackFor NullPointerException.class) // 错误设置public void createUser(UserInfo user) {userDao.save(user); // 可能会抛出 Exception// 假设下面这行会抛出一个 RuntimeExceptionUserLoginInfo userLoginInfo null; // 这里故意设置为 nulluserLoginInfo.setUserId(user.getUserId()); // 这里会抛出 NullPointerExceptionuserLoginService.saveUserLoginInfo(userLoginInfo);}
}测试类
public class TransactionTest {Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user new UserInfo();user.setUserId(1);userService.createUser(user);}
}如果 userDao.save(user) 方法抛出一个非 NullPointerException 类型的异常例如 SQLException则由于 rollbackFor 属性不包含该异常类型事务不会回滚。即使后续代码中发生了 NullPointerException由于前面的操作已经导致事务处于失败状态整个事务也不会被正确处理。
3.2异常被内部 catch 块捕获而导致 Transactional 注解失效的情况
定义用户服务类
Service
public class UserService {Autowiredprivate UserDao userDao;Autowiredprivate UserLoginService userLoginService;Transactional(rollbackFor Exception.class)public void createUser(UserInfo user) {try {userDao.save(user); // 假设这可能抛出 SQLException} catch (SQLException e) {// 捕获异常事务不会回滚System.out.println(Caught SQLException: e.getMessage());// 不重新抛出异常导致事务管理失效}// 下面的代码仍然会被执行UserLoginInfo userLoginInfo new UserLoginInfo();userLoginInfo.setUserId(user.getUserId());userLoginService.saveUserLoginInfo(userLoginInfo);}
}测试类
public class TransactionTest {Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user new UserInfo();user.setUserId(1);userService.createUser(user);}
}当 userDao.save(user) 抛出 SQLException 时该异常被 catch 块捕获并处理。 由于异常没有被重新抛出Transactional 注解无法检测到异常的发生因此事务不会回滚。 这意味着即使 userDao.save(user) 失败后续的 userLoginService.saveUserLoginInfo(userLoginInfo) 仍然会被执行导致数据的不一致。
3.3事务传播行为不当而导致 Transactional 注解失效的情况
定义用户服务类
Service
public class UserService {Autowiredprivate UserDao userDao;Autowiredprivate UserLoginService userLoginService;Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)public void createUser(UserInfo user) {userDao.save(user); // 可能抛出异常// 调用另一个方法假设这个方法没有Transactional注解userLoginService.saveUserLoginInfo(user.getUserId());}
}用户登录服务类
Service
public class UserLoginService {Autowiredprivate UserLoginDao userLoginDao;public void saveUserLoginInfo(Long userId) {// 假设这个方法执行一些 DML 操作// 但这里没有 Transactional 注解userLoginDao.saveLoginInfo(userId);}
}测试类
public class TransactionTest {Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user new UserInfo();user.setUserId(1);userService.createUser(user);}
}createUser 方法的传播行为被设置为 Propagation.REQUIRES_NEW
当 createUser 方法被调用时会启动一个新的事务。在该新事务中如果 userDao.save(user) 抛出异常整个事务将回滚。然而userLoginService.saveUserLoginInfo(user.getUserId()) 方法没有被 Transactional 注解包裹且它是在 createUser 方法的同一事务中调用的。
关键问题
因为 saveUserLoginInfo 方法没有事务管理所以即使 createUser 方法中的事务因为异常回滚saveUserLoginInfo 方法中的操作仍然会被提交。这导致了数据的不一致性因为在 createUser 方法失败时saveUserLoginInfo 方法的 DML 操作仍然会被执行。