springboot中的事务是由AOP代理进行的,首先代理开启事务,执行自身代码,提交事务。当非事务方法内调用类内的事务方法时,会发生事务失效。因为这样的调用是通过this调用的,this并不是代理对象。
在软工学习项目的上传图片模块,需要将图片上传到minio,并保存信息到数据库。由于上传到minio是网络请求,可能会消耗大量的时间而导致事务一直无法提交,消耗数据库资源。因此将事务注解@Transactional
加在保存数据库的方法上(为了提高代码复用性,单独抽离成了一个方法)。但这就导致了事务失效的问题。
1 2 3 4 5 6 7 8 9 10 11
| public UploadFileResultDto uploadFile(Long companyId, MultipartFile filedata){ MediaFiles mediaFiles = addMediaFilesToDb(companyId, FileMd5,uploadFileParamsDto,bucket_mediafiles, objectName); }
@Transactional public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){ }
|
解决事务失效:
1 2 3 4 5 6
| @Slf4j @Service public class MediaFileServiceImpl implements MediaFileService { @Autowired MediaFileService currentProxy; }
|
此方法还需要在接口类(即MediaFileService
)中暴露你需要调用的方法。
1 2 3
| public interface MediaFileService { public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName); }
|
这样通过currentProxy
调用类内的事务方法,就可以避免事务失效,因为此时currentProxy
对象是代理对象。
1 2 3 4 5 6
| public UploadFileResultDto uploadFile(Long companyId, MultipartFile filedata){ MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, FileMd5,uploadFileParamsDto,bucket_mediafiles, objectName); }
|
缺点是可能造成循环依赖,springboot可以解决比较简单的循环依赖,但在比较复杂的情景下可能会失效。
将数据库方法抽离到另一个service类中,也可以避免事务失效
1 2 3 4 5 6 7 8 9 10 11 12
| public class OriginalService { @Resource private TransactionTemplate transactionTemplate; public UploadFileResultDto uploadFile(...) { MediaFiles mediaFiles = transactionTemplate.execute(status -> { return addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket, objectName); }); } }
|
在Spring配置中启用AspectJ模式:
1
| @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
|