项目场景:
提示:这里简述项目相关背景:
开发中一直有看到一个警告:“注意严格控制事务时间,避免出现长事务抢占数据库连接,禁止将纯查询功能纳入事务范围。”因为并没有阻断程序功能和代码审核,所以一直也没有在意。最近正好想整理下事务的使用和注意事项。就由此展开吧。
什么是事务?
提示:这里描述项目中遇到的问题:
事务(Transaction) 是数据库管理系统中的一个重要概念,它表示一组不可分割的操作序列,这些操作要么全部成功执行,要么全部不执行。事务是数据库操作的逻辑单位,用于保证数据库的完整性和一致性。
事务的四个基本特性(ACID)
原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个点。
一致性(Consistency):事务必须使数据库从一个一致性状态转变到另一个一致性状态。
隔离性(Isolation):并发执行的事务之间不会相互影响。
持久性(Durability):一旦事务提交,则其所做的修改会永久保存在数据库中,即使系统发生故障。
(注意事项:
1、事务的大小:尽量保持事务小而短,以减少锁定资源的时间,提高并发性能。
2、死锁:注意避免事务中的死锁情况,可以通过合理的设计和优化事务的执行顺序来减少死锁发生。
3、性能监控:监控事务的性能,特别是在高并发环境下,确保系统能够处理大量的事务请求。)
案例分析:
提示:这里填写问题的分析:
由于上述警告一直存在,看不惯他于是就想着解决一下,项目中是通过使用@Transactional注解来实现事务控制的,但是正如提示所说禁止将纯查询功能纳入事务范围,然而,代码是这样的(挺经典的):
1@Transactional(rollbackFor = Exception.class)
2 public void downGraphicFile(String userId, String themeId, String downloadPurpose, HttpServletResponse response) {
3 if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(themeId)) {
4 throw new ApplicationException(GraphicBizErrorCodeType.EMPTY_RESOURCE_ID);
5 }
6 ThemeSearchRelDO searchRelDO = new ThemeSearchRelDO();
7 searchRelDO.setThemeId(themeId);
8 List<ThemeSearchRelDO> doList = themeSearchDomainService.findTempThemeList(searchRelDO);
9 List<GraphicInfoDO> busiInfoDOList = busiInfoDomainService.findListByThemeId(themeId);
10
11 ossAdapter.downloadZip(busiInfoDOList, "XX中心-" + doList.get(0).getThemeName(), response);
12
13 UserOperTransfer transfer = new UserOperTransfer(null, ActionType.DOWNLOAD, GraphicShape.THEME, false);
14 transfer.setDownloadPurpose(downloadPurpose);
15 userOperService.insertUserOper(themeId, userId, transfer);
16 themeService。updateInfo(themeId);
16 log.info("something~");
17 }
可以看到,这段代码通过@Transactional实现事务控制,rollbackFor = Exception.class 表示遇到异常就会回滚。
但是,这段事务控制做的事情太多太冗杂了。
首先,是第8行,第9行的查询方法,是不允许写在事务里的,因为查询操作没有使用事务的必要。
其次,是第11行的 ossAdapter.downloadZip() 方法,是一个调用第三方服务的下载方法,如果这个调用失败,它可能不会抛出异常,而是返回错误码或其他错误处理机制。而且网络延迟和超时(高负载或服务不可用)可能会导致事务长时间保持打开状态,从而影响系统性能和事务的一致性。且在这里也没有必要放在事务中(当然也有涉及使用分布式事务管理器来协调跨多个服务或资源的事务。但这种情况需要特别处理)
然后,是最后的日志打印,非核心业务,不建议放在事务中。
其实我们仔细阅读代码后能知道,真正需要事务处理的,只有15行和16行的插入操作和更新操作。
所以我们将它提取出来:
public void downGraphicFile(String userId, String themeId, String downloadPurpose, HttpServletResponse response) {
if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(themeId)) {
throw new ApplicationException(GraphicBizErrorCodeType.EMPTY_RESOURCE_ID);
}
ThemeSearchRelDO searchRelDO = new ThemeSearchRelDO();
searchRelDO.setThemeId(themeId);
List<ThemeSearchRelDO> doList = themeSearchDomainService.findTempThemeList(searchRelDO);
List<GraphicInfoDO> busiInfoDOList = busiInfoDomainService.findListByThemeId(themeId);
ossAdapter.downloadZip(busiInfoDOList, "XX中心-" + doList.get(0).getThemeName(), response);
UserOperTransfer transfer = new UserOperTransfer(null, ActionType.DOWNLOAD, GraphicShape.THEME, false);
transfer.setDownloadPurpose(downloadPurpose);
userOperService.insertUserOper(themeId, userId, transfer);
themeService。updateInfo(themeId);
log.info("something~");
}
@Transactional(rollbackFor = Exception.class)
// 需改成public
*private* void handleRecord(String themeId, String userId){
UserOperTransfer transfer = new UserOperTransfer(null, ActionType.DOWNLOAD, GraphicShape.THEME, false);
transfer.setDownloadPurpose(downloadPurpose);
userOperService.insertUserOper(themeId, userId, transfer);
themeService。updateInfo(themeId);
}
private 报红线,提示不能用private,因为 @Transactional 不能修饰私有方法,我们改成public。
那这样就可以了吗,答案是否定的,当我们测试执行的时候,会发现事务并没有生效,因为 在类内部,非 @Transactional 注解的方法调用 @Transactional 注解的方法,事务不会生效。
原因在于Spring的代理机制和事务的传播方式。Spring的声明式事务管理是基于AOP(面向切面编程)实现的,它通过为Bean创建代理来实现事务管理。当一个类被 @Transactional 注解标记时,Spring容器会为这个类创建一个代理对象,这个代理对象负责在方法执行前后添加事务管理的逻辑。
这里实际上是同一个对象内部的直接方法调用,而不是通过代理对象进行的调用。因为代理对象只在外部调用时起作用,内部调用实际上是绕过了Spring的代理机制。
解决方案:
提示:这里填写该问题的具体解决方案:
我们只需要把提出来的方法放到其他类中(下一层),实现Spring的代理机制,即可解决。
最后,附上事务失效/不生效的原因:
1、方法修饰符非public:@Transactional 注解标注的方法修饰符如果不是 public,则事务将不会生效。
2、类内部非 @Transactional 注解的方法调用 @Transactional 注解的方法:在类内部,非 @Transactional 注解的方法调用 @Transactional 注解的方法时,事务不会生效。
3、事务方法内部捕捉了异常,没有抛出新的异常:如果事务方法内部捕获了异常并且没有抛出新的异常,那么事务可能不会回滚。
4、@Transactional 注解属性 propagation 设置错误:如果错误配置了 propagation 属性,事务可能不会回滚。例如,使用 TransactionDefinition.PROPAGATION_SUPPORTS、TransactionDefinition.PROPAGATION_NOT_SUPPORTED 或 TransactionDefinition.PROPAGATION_NEVER 时。
5、数据库引擎不支持事务:如果数据库引擎不支持事务(如 MySQL 的 MyISAM 引擎),那么事务将失效。
6、类或方法被声明为 final:如果类或方法被声明为 final,则 Spring 无法创建其代理类,导致 @Transactional 注解失效。
7、类未被 Spring 容器管理:只有由 Spring 容器管理的 Bean 才能应用事务代理。如果类未被正确注册为 Spring Bean(例如,缺少 @Component、@Service 注解),事务管理将不起作用。
8、事务管理器配置错误:如果事务管理器没有正确配置,也会导致事务失效。
为了避免这些问题,确保:
使用 @Transactional 的方法是 public 的。
不要在内部方法调用中使用 @Transactional,而是通过 Spring 代理来调用。
在事务方法中正确处理异常,确保需要回滚的异常能够被抛出。
正确设置 @Transactional 的 propagation 属性。
使用支持事务的数据库引擎。
避免将需要事务管理的类或方法声明为 final。
确保类被 Spring 容器管理,添加适当的注解。
正确配置事务管理器。


7061

被折叠的 条评论
为什么被折叠?



