事务的优化

news2024/10/5 20:25:16

 例子:

举例:假设我们有一个文件上传的uploadFile方法,在这个方法中我们会先执行上传一个文件到分布式文件系统中的方法addMediaFilesToMinIO( ),上传成功后执行文件资源数据入库的addMediaFilesToDb( ),那么这个时候事务应该加在哪个方法上? 是否会有事务失效的问题?

 问题引出

先说结论:我们只将addMediaFilesToDb方法添加事务控制即可,uploadFile方法上的不需要加@Transactional注解,只给与本地数据库进行网络传输的方法加相应的注解,在开发时,不同的网络传输方法要抽离成单独的方法,但是要注意事务生效的条件。

正常情况下我们在外部uploadFile方法上加会生效,但是直接将@Transactional加在addMediaFilesToDb( )事务会失效,问题出在哪里呢?

原因分析

如果在uploadFile方法上添加@Transactional注解,代理对象执行此方法前会开启事务,如下图:

如果在uploadFile方法上没有@Transactional注解,代理对象执行此方法前不进行事务控制,如下图:

所以判断该方法是否可以事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解。

现在在addMediaFilesToDb方法上添加@Transactional注解,也不会进行事务控制是因为并不是通过代理对象执行的addMediaFilesToDb方法。为了判断在uploadFile方法中去调用addMediaFilesToDb方法是否是通过代理对象去调用,我们可以打断点跟踪。

我们发现在uploadFile方法中去调用addMediaFilesToDb方法不是通过代理对象去调用。

如何解决呢?通过代理对象去调用addMediaFilesToDb方法即可解决。

先将addMediaFilesToDb方法提成接口

 /**
  * @description 将文件信息添加到文件表
  * @param companyId  机构id
  * @param fileMd5  文件md5值
  * @param uploadFileParamsDto  上传文件的信息
  * @param bucket  桶
  * @param objectName 对象名称
  * @return com.xuecheng.media.model.po.MediaFiles
  * @author Mr.M
  * @date 2022/10/12 21:22
  */
 public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);

在MediaFileService的实现类中注入MediaFileService的代理对象(注入自己交托spring管理成为代理对象),如下:

@Autowired
MediaFileService currentProxy;

在uploadFile方法中将调用addMediaFilesToDb方法处代码改为

MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);

再次debug可以看到对象为代理对象,并且可以通过加入int i = 1/0,观察十五是否回滚测试addMediaFilesToDb上加事务是否生效。

 

完整代码

package com.xuecheng.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.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.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.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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 Mr.M
 * @version 1.0
 * @description TODO
 * @date 2022/9/10 8:58
 */
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {

    @Autowired
    MediaFilesMapper mediaFilesMapper;
    @Autowired
    MinioClient minioClient;
    @Autowired
    MediaFileService currentProxy;
// 存储普通文件
    @Value("${minio.bucket.files}")
    private String bucket_files;
// 存储视频文件
    @Value("${minio.bucket.videofiles}")
    private String bucket_video;

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

    @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;
    }


    //获取文件默认存储目录路径 年/月/日
    private String getDefaultFolderPath() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/") + "/";
        return folder;
    }

    //获取文件的md5
    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;
        }
    }

    /**
     * 根据扩展名获取MimeType
     * @param extension
     * @return
     */
    private String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        //根据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        //通用mimeType,字节流
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

    /**
     * 将文件上传到minio
     * @param localFilePath 文件地址
     * @param bucket        桶
     * @param objectName    对象名称
     * @return void
     * @description 将文件写入minIO
     * @author Mr.M
     * @date 2022/10/12 21:22
     */
    public boolean addMediaFilesToMinIO(String localFilePath, String mimeType, String bucket, String objectName) {
        try {
            UploadObjectArgs testbucket = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .filename(localFilePath)
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(testbucket);
            log.debug("上传文件到minio成功,bucket:{},objectName:{}", bucket, objectName);
            System.out.println("上传成功");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}", bucket, objectName, e.getMessage(), e);
            XueChengPlusException.cast("上传文件到文件系统失败");
        }
        return false;
    }

    /**
     * @param companyId           机构id
     * @param fileMd5             文件md5值
     * @param uploadFileParamsDto 上传文件的信息
     * @param bucket              桶
     * @param objectName          对象名称
     * @return com.xuecheng.media.model.po.MediaFiles
     * @description 将文件信息添加到文件表
     * @author Mr.M
     * @date 2022/10/12 21:22
     */
    @Override
    @Transactional  // 在这里加注解
    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);
            mediaFiles.setId(fileMd5);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setUrl("/" + bucket + "/" + objectName);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath(objectName);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setAuditStatus("002003");
            mediaFiles.setStatus("1");
            //保存文件信息到文件表
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert < 0) {
                log.error("保存文件信息到数据库失败,{}", mediaFiles.toString());
                XueChengPlusException.cast("保存文件信息失败");
                return null;
            }
            log.debug("保存文件信息到数据库成功,{}", mediaFiles.toString());
        }
        return mediaFiles;

    }

    /**
     * 上传文件
     *
     * @param companyId           机构id
     * @param uploadFileParamsDto 上传文件信息
     * @param localFilePath       文件磁盘路径
     * @return
     */
//    @Transactional 不要给这个方法加事务注解,细化到数据库传输方法加
    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
        File file = new File(localFilePath);
        if (!file.exists()) {
            XueChengPlusException.cast("文件不存在");
        }
        //文件名称
        String filename = uploadFileParamsDto.getFilename();
        //文件扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        //文件mimeType
        String mimeType = getMimeType(extension);
        //文件的md5值
        String fileMd5 = getFileMd5(file);
        //文件的默认目录
        String defaultFolderPath = getDefaultFolderPath();
        //存储到minio中的对象名(带目录)
        String objectName = defaultFolderPath + fileMd5 + extension;
        //将文件上传到minio
        boolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);
        if (!result){
            XueChengPlusException.cast("上传文件失败!");
        }
        //文件大小
        uploadFileParamsDto.setFileSize(file.length());
        //将文件信息存储到数据库
        MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);
        if (null==mediaFiles){
            XueChengPlusException.cast("文件上传后保存信息失败");
        }
        //准备返回数据
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
        return uploadFileResultDto;
    }
}

结论

       在使用事务注解的时候要考虑与三方等网络传输的效率等问题,正确的使用事务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/974507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年7月京东彩妆市场品牌销售排行榜(京东数据挖掘)

鲸参谋监测的京东平台7月份彩妆市场销售数据已出炉&#xff01; 鲸参谋数据显示&#xff0c;7月份彩妆市场整体呈现下滑趋势。从大盘数据可知&#xff0c;京东7月的销量将近350万&#xff0c;环比下滑约38%&#xff0c;同比下滑约22%&#xff1b;销售额为5.1亿&#xff0c;环比…

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书南宁师范大学图书馆

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书南宁师范大学图书馆

深度学习-4-二维目标检测-YOLOv5理论模型详解

YOLOv5理论模型详解 1.Yolov5四种网络模型 Yolov5官方代码中&#xff0c;给出的目标检测网络中一共有4个版本&#xff0c;分别是Yolov5s、Yolov5m、Yolov5l、Yolov5x四个模型。 YOLOv5系列的四个模型&#xff08;YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x&#xff09;在参数量和性…

什么是ETLT?他是新一代数据集成平台?

什么是ETLT&#xff1f; 在现代数据处理和分析的时代&#xff0c;数据集成是一个至关重要的环节。数据集成涉及将来自各种来源的数据合并、清洗、转换&#xff0c;并将其加载到数据仓库或分析平台以供进一步的处理和分析。传统上&#xff0c;数据集成有两种主要方法&#xff0…

港陆证券:股票中线投资?

股票是一种充溢变数的出资方法&#xff0c;长时刻出资与短期交易带来的危险与收益是不一样的&#xff0c;中线出资则是在两者之间的一种折中计划&#xff0c;既能取得必定的收益&#xff0c;又能削减必定的危险&#xff0c;而且比起短期操作&#xff0c;中线出资愈加符合快节奏…

线性表之-单向链表(无头)

目录 什么是单向链表 顺序表和链表的区别和联系 顺序表&#xff1a; 链表&#xff1a; 链表表示(单项)和实现 1.1 链表的概念及结构 1.2单链表(无头)的实现 所用文件 将有以下功能&#xff1a; 链表定义 创建新链表元素 尾插 头插 尾删 头删 查找-给一个节点的…

linux 进程隔离Namespace 学习

一、linux namespace 介绍 1.1、概念 Linux Namespace是Linux内核提供的一种机制&#xff0c;它用于隔离不同进程的资源视图&#xff0c;使得每个进程都拥有独立的资源空间&#xff0c;从而实现进程之间的隔离和资源管理。 Linux Namespace的设计目标是为了解决多个进程之间…

从零编写STM32H7的MDK SPI FLASH下载算法

从零编写STM32H7的MDK SPI FLASH下载算法 - 知乎 Part1前言 当我们要下载编译好的镜像到Flash时&#xff0c;首先要做的一步就是选择合适的Flash下载算法&#xff0c;而这个算法本身就是一个FLM文件&#xff1a; 代码既可以下载到内部flash&#xff0c;也可以下载到外部flash&…

pyinstaller打包exe运行闪退

这里写自定义目录标题 前言问题描述解决过程 前言 闪退原因可能有很多&#xff0c;这里记录下我遇到的问题&#xff0c;简单来说是dll调用错误导致的闪退&#xff0c;因为我的python用的是32位的&#xff0c;但是pyinstaller却是64位的&#xff0c;属于用conda的时候没注意。 …

【C#】C#调用进程打开一个exe程序

文章目录 一、过程二、效果总结 一、过程 新建WinForm程序&#xff0c;并写入代码&#xff0c;明确要调用的程序的绝对路径&#xff08;或相对路径&#xff09;下的exe文件。 调用代码&#xff1a; 这里我调用的另一个程序的路径是&#xff1a; F:\WindowsFormsApplication2…

Python自动化写作神器:告别语法拼写错误的秘诀

概要 写作是一种常见的沟通方式&#xff0c;无论是在学习、工作还是生活中&#xff0c;我们都需要用文字来表达自己的想法和观点。但是&#xff0c;写作并不是一件容易的事情&#xff0c;尤其是当我们使用非母语时&#xff0c;很容易出现语法和拼写错误&#xff0c;影响了文章…

javaScript:DOM中的CSS操作

目录 1.style 属性获取元素写在行间的样式 2.getComputedStyle(元素对象&#xff0c;null)可以获取元素的非行间样式 3.案例&#xff08;定义一个div和按钮&#xff0c;每点击一次按钮div宽度增加&#xff09; 效果预览图 代码实现 在 JavaScript 中&#xff0c;可以通过…

【8章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第8章 Spark MLlib&#xff08;6节&#xff09; 机器学习算法库 &#xff08;一&#xff09;MLlib简介 1、机器学习 机器学习可以看做是一门人工智能的科学&#xff0c;该领…

【前端面试】快来看看这8个高级面试题

目录 前言1、仔细观察 和 - 运算符2、复制数组元素3、原型和__proto__之间的区别4、范围5、对象强制6、理解对象键7、运算符8、闭包 前言 JavaScript 是一种功能强大的语言&#xff0c;是网络的主要构建块之一。这种强大的语言也有一些怪癖。例如&#xff0c;您是否知道 0 -…

ES查询报错内容长度超过104857600

项目场景&#xff1a; 使用 ElasticsearchRestTemplate 或者使用 RestHighLevelClient 查询 ES 报错 内容长度超过 104857600 问题描述 ES 查询报错 entiity content is too long xxx for the configured buffer limit 104857600 Overridepublic void esQuery() {restHighL…

能耗管理+分区温控成为开发节能、省电神器的关键!从此告别电费刺客时代

取暖器在人们脑海中最深刻的印象&#xff0c;就是费电&#xff01;而它耗电量大的原因&#xff0c;主要在于它是靠电能直接转化为热能&#xff1a;在取暖设备通电后&#xff0c;内部高电阻的电热丝发热&#xff0c;风机会将这股热量吹散到室内&#xff0c;从而达到全屋取暖的效…

OpenCV基础知识(10)— 人脸识别(人脸跟踪、眼睛跟踪、行人跟踪、车牌跟踪和人脸识别)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别技术&#xff0c;也是计算机视觉重点发展的技术。机械学习算法诞生之后&#xff0c;计算机可以通过摄像头等输入设备自动分析图像中包含的内容信息&#xff0c;随…

SpringCloud从基础到活用(超详细)

一、认识微服务 项目的架构方式有&#xff1a;单体架构、分布式架构、微服务架构- 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;项目架构也从单体架构逐渐演变为现在流行的微服务架构。 - 这些架构之间有怎样的差别呢&#xff1f;1、单体架构 **单体…

Linux设备驱动程序

一、设备驱动程序简介 图1.1 内核功能的划分 可装载模块 Linux有一个很好的特性:内核提供的特性可在运行时进行扩展。这意味着当系统启动 并运行时&#xff0c;我们可以向内核添加功能( 当然也可以移除功能)。 可在运行时添加到内核中的代码被称为“模块”。Linux内核支持好几…

Si24R2F+畜牧 耳标测体温开发资料

Si24R2F是针对IOT应用领域推出的新款超低功耗2.4G内置NVM单发射芯片。广泛应用于2.4G有源活体动物耳标&#xff0c;带实时测温计步功能。相较于Si24R2E&#xff0c;Si24R2F增加了温度监控、自动唤醒间隔功能&#xff1b;发射功率由7dBm增加到12dBm&#xff0c;距离更远&#xf…