1.调用这个方法的对象是否是spring的代理对象($CGLIB结尾的)
2.这个方法是否是加了@Transactional注释
都符合才可以被事物控制
如果调用方法的对象没有被事物控制,那么被调用的方法即便是加了@Transactional也是没用的
事务失效情况:
解释说明:
1)基于aop的环绕通知的方式,如果抛出了异常给spring框架和代理对象就会进行事物的回滚,而如果调用这个方法的时候把异常捕获到了,并没有抛出就会导致事务无法回滚。
3)事务方法调用事务方法:
如果在事务对象内部直接调用另一个事务方法,那么是会进行事务传递的,被调用的事务方法是会被当成和调用方一个整体的事务。并且被调用的事务不可以新开启一个事务,也就是说,被调用的事务上方加上如下注释是不起作用的
5)可以自行定义事务抛出什么类型的异常才能回滚:
一个非事务方法调同类一个事务方法,事务无法控制举例如下:
在controller中,调用MediaFileServiceImpl 中的uploadFile方法,因为controller中注入了@Autowired MediaFileService mediaFileService;所以controller将被spring代理对象代理,此时如果MediaFileServiceImpl中的 uploadFile加了@Transactional注释,那么很自然的将受到事物控制。(验证是否是spring的代理对象,可以debug然后查看变量是否是$CGLIB结尾的)。
如果在uploadFile方法上没有@Transactional注解,代理对象执行此方法前不进行事务控制,如下图:
现在在addMediaFilesToDb方法上添加@Transactional注解,也不会进行事务是因为并不是通过代理对象执行的addMediaFilesToDb方法。为了判断在uploadFile方法中去调用addMediaFilesToDb方法是否是通过代理对象去调用,我们可以打断点跟踪。
controller代码如下,在try catch中看到用的是mediaFileService去调用的uploadFile方法,此时是可以控制事物的。
public class MediaFilesController {
@Autowired
MediaFileService mediaFileService;
@RequestMapping(value = "/upload/coursefile", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata,
@RequestParam(value = "folder",required=false) String folder,
@RequestParam(value= "objectName",required=false) String objectName) {
Long companyId = 1232141425L;
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
String contentType = filedata.getContentType();
uploadFileParamsDto.setContentType(contentType);
uploadFileParamsDto.setFileSize(filedata.getSize());//文件大小
if (contentType.indexOf("image") >= 0) {
//是个图片
uploadFileParamsDto.setFileType("001001");
} else {
uploadFileParamsDto.setFileType("001003");
}
uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件名称
UploadFileResultDto uploadFileResultDto = null;
try {
uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, filedata.getBytes(), folder, objectName);
} catch (Exception e) {
XueChengPlusException.cast("上传文件过程中出错");
}
return uploadFileResultDto;
}
如果在uploadFile方法中取调用另外一个方法,那么显然调用的对象默认是this,并不是受到spring代理的对象,所以即便被uploadFile方法调用的方法加了@Transactional注释,也是没用的。
解决非代理对象调用@Transactional方法的:
我们可以模仿@Controller中的写法,在被调用的方法中注入相应的service,然后用service在非事物方法中取调用加了@Transactional注释的方法,这样事物是会生效的。
下面举例:
在serviceimpl类中的uploadFile方法调用另一个@Transactional方法,形成事物
首先在serviceimpl中注入service对象:(对象名字无所谓,加了@Autowired注释的对象会被AOP拦截)
@Autowired
MediaFileService proxy;
调用方法:(在try代码第2行,用proxy调用)
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
....
try {
addMediaFilesToMinIO(bytes,bucket_files,objectName);
MediaFiles mediaFiles = proxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);
//准备返回数据
UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
return uploadFileResultDto;
} catch (Exception e) {
log.debug("上传文件失败:{}",e.getMessage());
throw new RuntimeException(e.getMessage());
}
// return null;
}
被调用的方法:
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId, String fileId, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {
//保存到数据库
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileId);
if(mediaFiles == null){
mediaFiles = new MediaFiles();
//封装数据
BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
mediaFiles.setId(fileId);
mediaFiles.setFileId(fileId);
mediaFiles.setCompanyId(companyId);
mediaFiles.setBucket(bucket);
mediaFiles.setFilePath(objectName);
mediaFiles.setUrl("/"+bucket+"/"+objectName);
mediaFiles.setCreateDate(LocalDateTime.now());
mediaFiles.setStatus("1");
mediaFiles.setAuditStatus("002003");
//插入文件表
mediaFilesMapper.insert(mediaFiles);
}
return mediaFiles;
}
注意:我们这里由于是用的service来调用,而service是一个接口,所以被proxy调用的方法必须也是以接口形式呈现出来,那么需要将addMediaFilesToDb写入到对应调用这个方法的service(此处用)MediaFileService proxy调用,所以在MediaFileService 中加上这个方法,写个接口即可。
接口如下:
public interface MediaFileService {
/***
* @description 上传文件到数据库,抽取为接口的形式,方便调用
* @param companyId
* @param fileId
* @param uploadFileParamsDto
* @param bucket
* @param objectName
* @return
* @author
* @date
*/
public MediaFiles addMediaFilesToDb(Long companyId, String fileId, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName);
}