Spring 事务失效

总结点 什么情况下会导致事务失效

代理对象调用
方法上添加Transcation 注解 采用声明式事务
1)方法中捕获异常没有抛出
spring控制事务式基于AOP环绕通知实现的,如果方法抛出了异常就会回滚,如果没有抛出就不会生效
2)非事务方法调用事务方法
在内部调用由于不是代理对象,因此失效
3)事务方法调用事务方法
可以被控制 但要选对事务传播行为 只要不是通过代理对象去掉 那么开启一个新事务是不可能的
4)transaction 如果标记的不是public 方法也不可以
5)抛出的异常类型 默认是RuntimeException
6) 数据库本身不支持事务 例如mysql 的myisam存储引擎
7)传播行为导致事务失效 比如某些事务传播行为 压根不支持事务

下面给出一个非事务方法调用事务方法时 不是代理对象的解决方案

方案一 编程式事务
这个就没什么好说的了,网上很多

方案二 声明式事务
首先分析原因式因为没有采用代理对象调用,因为该事务方法与非事务方法定义在一个service中,那么就需要想办法将其变为代理对象调用,我们知道注入的对象为代理对象,因此在本service中注入本service。
但是又引申出另一个问题,本service 注入本service 会造成循环依赖,本循环依赖并不会通过spring 三级缓存解决,需要使用@Lazy 注解延迟加载来打破循环依赖。
补充:什么是三级缓存
第一级 单例对象缓存池,已经实例化并且属性赋值,存放成熟对象
第二级 单例对象缓存池,已经实例化但尚未付属性值,存放半成品对象
第三级 单例工厂的缓存

package com.jhj.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.jhj.base.exception.UltimateException;
import com.jhj.base.model.PageParams;
import com.jhj.base.model.PageResult;
import com.jhj.media.mapper.MediaFilesMapper;
import com.jhj.media.model.dto.QueryMediaParamsDto;
import com.jhj.media.model.dto.UploadFileParamsDto;
import com.jhj.media.model.dto.UploadFileResultDto;
import com.jhj.media.model.po.MediaFiles;
import com.jhj.media.service.MediaFileService;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * @author jhj
 * @version 1.0
 * @description TODO
 * @date 2022/9/10 8:58
 */
@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {
    @Autowired
    MediaFilesMapper mediaFilesMapper;

    @Autowired
    MinioClient minioClient;

    @Autowired
    @Lazy
    MediaFileService currentProxy;

    //存储普通文件
    @Value("${minio.bucket.files}")
    private String bucket_mediafiles;

    //存储视频
    @Value("${minio.bucket.videofiles}")
    private String bucket_video;


    @Override
    public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {

        //构建查询条件对象
        LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();

        //分页对象
        Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        // 查询数据内容获得结果
        Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
        // 获取数据列表
        List<MediaFiles> list = pageResult.getRecords();
        // 获取数据总数
        long total = pageResult.getTotal();
        // 构建结果集
        PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
        return mediaListResult;

    }

    @Override
    //为什么不在这里加事务 是因为防止minio 网络连接时间长而导致数据库连接超时 占用数据库事务资源
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
        //文件名
        String filename = uploadFileParamsDto.getFilename();
        //拿到扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        //拿到mimeType
        String mimeType = getMimeType(extension);
        //文件的md5
        String fileMd5 = getFileMd5(new File(localFilePath));
        //目录
        String defaultFolderPath = getDefaultFolderPath();
        String objectName = defaultFolderPath + fileMd5 + extension;
        //将文件上传Minio
        boolean result = addMediaFilesToMinio(localFilePath, mimeType, bucket_mediafiles, objectName);
        if(!result){
            UltimateException.cast("上传文件失败");
        }

        //将文件信息保存到数据库
        MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);
        if(mediaFiles==null){
            UltimateException.cast("文件上传后,保存信息失败");
        }

        //返回对象
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
        return uploadFileResultDto;
    }

    //根据扩展名获取mimeType
    private String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE; //通用mimeType
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

    /**
     * 将文件上传到minio
     * @param localFilePath 本地路径
     * @param mimeType 媒体类型
     * @param bucket 桶
     * @param objectName 文件名
     * @return
     */
    private boolean addMediaFilesToMinio(String localFilePath, String mimeType, String bucket, String objectName) {
        try {
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                    .bucket(bucket)
                    .filename(localFilePath)
                    .object(objectName)
                    .contentType(mimeType)
                    .bucket(bucket).build()
            );
            log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());
        }
        return false;
    }
    
    private String getDefaultFolderPath(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/") + "/";
        return folder;
    }

    private String getFileMd5(File file){
        try(FileInputStream fileInputStream=new FileInputStream(file)){
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将文件信息保存到数据库
     * @param companyId 机构id
     * @param fileMd5 md5
     * @param uploadFileParamsDto 文件信息
     * @param bucket 桶信息
     * @param objectName 路径
     * @return
     */
    @Transactional
    //事务只能用于public 方法 原理是Aop jdk cglib 两种方式 访问不了private
    //注意看是不是被代理对象调用 如果不是 则事务不会生效
    //同serive 调用同类的事务方法 事务是无法控制的 因为用的是this 并不是代理对象
    //注入进去的都是代理对象 this就是对象本身 解决办法 把service 在本类中进行注入
    public MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles == null){
            mediaFiles=new MediaFiles();
            BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
            //文件id
            mediaFiles.setId(fileMd5);
            mediaFiles.setFileId(fileMd5);
            //机构id
            mediaFiles.setCompanyId(companyId);
            //桶
            mediaFiles.setBucket(bucket);
            //file_path
            mediaFiles.setFilePath(objectName);
            //url
            mediaFiles.setUrl("/"+bucket+"/"+objectName);
            //上传时间
            mediaFiles.setCreateDate(LocalDateTime.now());
            //状态
            mediaFiles.setStatus("1");
            //审核状态
            mediaFiles.setAuditStatus("002003");
            //插入数据库
            int insert = mediaFilesMapper.insert(mediaFiles);
            int i=1/0;
            if(insert<0){
                log.error("向数据库保存文件失败,bucket:{},objectName:{}",bucket,objectName);
                return null;
            }
            return mediaFiles;
        }
        return mediaFiles;
    }
}
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值