聊一聊Spring中的@Transactional注解【下】【注解失效场景】
前言
尽管 @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 {
@Override
@Transactional(rollbackFor = Exception.class) // 注解在接口实现上
public void createUser(UserInfo user) {
// DML 操作
System.out.println("User created: " + user.getUserId());
}
}
- 配置Spring和使用CGLIB代理
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl(); // CGLIB 代理
}
@Bean
public DataSource dataSource() {
// 配置数据源
}
}
- 测试类
public class TransactionTest {
@Autowired
private 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 修饰的类
- 无法被代理:如果一个类被声明为
final
,Spring 无法对其进行子类化,因此无法生成代理。由于 Spring 的事务管理依赖于代理机制,@Transactional
注解将不会生效,相关的事务管理功能将被忽略。
1.3.2static 修饰的方法
- 无法代理:
static
方法属于类本身,而不是类的实例,因此 Spring 事务管理也无法对其进行代理。这意味着,即使在static
方法上使用@Transactional
,事务管理也不会生效。
1.4没有被Spring管理的bean
- Spring 的事务管理依赖于代理机制来处理方法调用。当你在一个类上使用
@Transactional
注解时,Spring 会创建一个代理对象,并在调用被注解的方法时应用事务逻辑。 - 如果一个 bean 没有被 Spring 管理(例如,它不是 Spring 上下文中的一个 bean),Spring 就不会为它创建代理,因此
@Transactional
注解将不起作用。
2、框架及底层角度
2.1非public修饰的方法
/**
* 创建代理时,会把方法和事务属性放到缓存对象attributeCache中
* 计算事务属性时,会判断方式是否public修饰
*/
// AbstractFallbackTransactionAttributeSource
private final Map<Object, 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)
@Async
public 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 {
@Autowired
private UserDao userDao;
@Autowired
private UserLoginService userLoginService;
@Transactional(rollbackFor = NullPointerException.class) // 错误设置
public void createUser(UserInfo user) {
userDao.save(user); // 可能会抛出 Exception
// 假设下面这行会抛出一个 RuntimeException
UserLoginInfo userLoginInfo = null; // 这里故意设置为 null
userLoginInfo.setUserId(user.getUserId()); // 这里会抛出 NullPointerException
userLoginService.saveUserLoginInfo(userLoginInfo);
}
}
- 测试类
public class TransactionTest {
@Autowired
private 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 {
@Autowired
private UserDao userDao;
@Autowired
private 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 {
@Autowired
private 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 {
@Autowired
private UserDao userDao;
@Autowired
private 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 {
@Autowired
private UserLoginDao userLoginDao;
public void saveUserLoginInfo(Long userId) {
// 假设这个方法执行一些 DML 操作
// 但这里没有 @Transactional 注解
userLoginDao.saveLoginInfo(userId);
}
}
- 测试类
public class TransactionTest {
@Autowired
private 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 操作仍然会被执行。
原文地址:https://blog.csdn.net/indolentSnail/article/details/143496064
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!