Mybatis整合Spring的事务控制与SqlSession线程安全问题

news2024/12/28 19:06:17

在Spring与Mybatis框架整合中,主要有两个重要改动,分别是事务与SqlSession。mybatis-spring包中为以上两个问题提供了解决方案。

  • 重要组件
  1. SpringManagedTransaction (Spring事务管理器)
  2. SqlSessionTemplate (SqlSession的实现)
  3. SqlSessionFactoryBean(整合中取代SqlSessionFactoryBuilder作用,创建SqlSessionFactory类)
  4. SpringManagedTransactionFactory(Spring事务工厂)
  • 整合流程

(1)在MybatisAutoConfiguration中给容器中注入了一个SqlSessionFactory 。

 @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);

而这个SqlSessionFactory 中的SpringManagedTransactionFactory,这个类实现了mybatis的顶级接口TransactionFactory,此时mybatis将不再管理事务,连接的获取和回滚也完全由Spring管理。

        targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));

这样的话每次在mapper执行相关方法时都会创建一个事务对象,这里自然就是创建的Spring的事务对象。

public class SpringManagedTransaction implements Transaction {
    private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
    private final DataSource dataSource;
    private Connection connection;
    private boolean isConnectionTransactional;
    private boolean autoCommit;

    public SpringManagedTransaction(DataSource dataSource) {
        Assert.notNull(dataSource, "No DataSource specified");
        this.dataSource = dataSource;
    }

    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        LOGGER.debug(() -> {
            return "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring";
        });
    }

    public void commit() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            LOGGER.debug(() -> {
                return "Committing JDBC Connection [" + this.connection + "]";
            });
            this.connection.commit();
        }

    }

    public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            LOGGER.debug(() -> {
                return "Rolling back JDBC Connection [" + this.connection + "]";
            });
            this.connection.rollback();
        }

    }

    public void close() throws SQLException {
        DataSourceUtils.releaseConnection(this.connection, this.dataSource);
    }

    public Integer getTimeout() throws SQLException {
        ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
        return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
    }
}

在openConnection的时候,事务对象是从DataSourceUtils.getConnection(this.dataSource);此时Spring会往当前线程中存一个Connection连接,那么在同一个方法中,多个mapper在获取connection时,其实都是获取的一个connection,当整个service方法执行结束时,Spring的事务管理器会统一执行commit方法。

(2)MybatisAutoConfiguration中同时给容器中注入了一个SqlSessionTemplate。

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

SqlSessionTemplate 替代了之前的sqlsession,用来解决mapper之间的线程安全问题。
SqlSessionTemplate的getMapper代码如下:

public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }

可以看到,此时依然是调用的configuration对象的getMapper方法,但是SqlSession对象传递的是this,而这里的this为sqlSessionTemplate对象,无论是通过mapper还是sqlId,在最终执行sql时,都是用的sqlSession对象。那么我们就看SqlSessionTemplate对象是如何执行sql的。

以selectList方法为例,我们发现sqlSessionTemplate中的相关方法都是通过sqlSessionProxy对象去执行的。

    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.selectList(statement);
    }

    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.selectList(statement, parameter);
    }

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
    }

而通过构造方法排查,这个sqlSessionProxy竟然是个SqlSession的代理对象。

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

代理对象的实现逻辑封装在了SqlSessionTemplate.SqlSessionInterceptor类中,我们只需要弄清这个类的实现原理即可。

果不其然这个类实现了InvocationHandler接口

  private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
                Object result = method.invoke(sqlSession, args);
                if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }

                unwrapped = result;
            } catch (Throwable var11) {
                unwrapped = ExceptionUtil.unwrapThrowable(var11);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }

                throw (Throwable)unwrapped;
            } finally {
                if (sqlSession != null) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }

            }

            return unwrapped;
        }
    }

在invoke方法中是通过 SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
来获取sqlSession的,我们跟进这个方法。

在这里插入图片描述
发现最终还是调用了openSession这个方法,但是这里要注意了,每次调用任何mapper的任何方法,都会重新开启一个openSession,每个session中的事务管理器也都是spring管理,这样的话就避免了线程安全问题,保证每一个mapper方法的调用都会有独立的sqlSession,同时事务又交由Spring管理。这样Mybatis可以与Spring进行整合,实现面向接口编程。

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

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

相关文章

新年快到了,教大家用汇编语言显示Happy New Year!

新年快到了&#xff0c;教大家用汇编来显示Happy New Year! 文章目录新年快到了&#xff0c;教大家用汇编来显示Happy New Year!汇编老矣&#xff0c;尚能饭否&#xff1f;特点效率底层显示字符安装环境编写代码内存地址空间显示格式代码编译连接运行总结大家都知道&#xff0c…

使用Docker搭建主从数据库(MySQL)

很多人对主从数据库有一个误区&#xff0c;把多个MySQL搭建在同一个服务器上&#xff0c;性能提升是不是很有限&#xff1f;这个理念是错误的&#xff0c;数据库的性能瓶颈主要是在IO和CPU负载过高&#xff0c;在同一台服务器运行多个数据库&#xff0c;能带来更低的延迟&#…

Java中atomic包中的原子操作类总结

1. 原子操作类介绍 在并发编程中很容易出现并发安全的问题&#xff0c;有一个很简单的例子就是多线程更新变量 i1,比如多个线程执行 i操作&#xff0c;就有可能获取不到正确的值&#xff0c;而这个问题&#xff0c;最常用的方法是通过 Synchronized 进行控制来达到线程安全的目…

【自学Python】Python复数(complex)

Python复数(complex) Python复数(complex)教程 Python 可以支持复数&#xff0c;复数的虚部用 j 或 J 来表示。如果需要在程序中对复数进行计算&#xff0c;需要导入 Python 的 cmath 模块&#xff0c;在该模块下包含了各种支持复数运算的函数。 案例 复数 定义 Python 中…

vim光速开发,你值得拥有

文章目录vim设计哲学vim的模式什么是可视模式光标移动动作(motion)操作符(operator)操作符&#xff08;operator&#xff09;动作&#xff08;motion&#xff09;实际使用大小写转换easymotionvim-surroundTIPSideavim的使用vim设计哲学 vim被称为编辑器之神。它的成名就是因为…

Python文档阅读笔记-Turn Images into Cartoons using Python

本博文说明如何将图片转为卡通风格。 1. 导入依赖模块 在编程的第一步首先要导入依赖库&#xff0c;在这个图像转换成卡通风格的程序中需要包含3个模块&#xff0c;分别是openCV&#xff0c;numpy&#xff0c;matpoltlib。 import cv2 import numpy as np import matplotlib.…

solidity Dapp 基于merkle的选择性披露合约——我的还是我的

现在生活中&#xff0c;大家为了隐私&#xff0c;并不希望直接将个人信息给别人看&#xff0c;比如我们去住酒店时&#xff0c;需要登记姓名、身份证号信息&#xff0c;但是如果我们直接把身份证给前台人员的话&#xff0c;前台人员就可以看到我们的民族、住址等信息。那么我们…

搭建我的世界java版服务器,公网远程联机【内网穿透】

文章目录1. 搭建我的世界服务器1.1 服务器安装java环境1.2 配置服务端2. 测试局域网联机3. 公网远程联机3.1 安装cpolar内网穿透3.1.1 windows系统3.1.2 linux系统&#xff08;支持一键自动安装脚本&#xff09;3.2 创建隧道映射内网端口3.3 测试公网远程联机4. 配置固定TCP端口…

利用mybatis对数据库中的数据进行增删改查操作

写在前面&#xff1a; 本篇文章的代码都是在上一篇文章的基础上增删改&#xff0c;本篇文章并不会出现所有的代码&#xff0c;如有需求可参考上篇文章传送门 namespace中的包名要和Dao/mapper接口的包名一致&#xff1a; 假设此时我们将接口名进行修改&#xff0c;而不改变映…

windows系统,计算机cmd管理员,命令行中普通用户获取管理员权限的命令

文章目录一、第一种方式&#xff1a;搜索框搜索二、第二种方式&#xff1a;winR &#xff08;这种方式作者没有找到进入管理员的方式&#xff09;三、普通方式进入&#xff0c;通过命令授予用户权限四、通过开始右键进入cmd参考文档一、第一种方式&#xff1a;搜索框搜索 以管理…

JLINK与 SWD接口

JLINK与 SWD接口 1.使用Jlink连接 Jlink驱动&#xff1a;SEGGER - The Embedded Experts - Downloads - J-Link / J-Trace pylink文档&#xff1a;PyLink — PyLink 2.读取内存地址 3.获取内存地址的默认值 register.py 芯片配置&#xff1a; 环境搭建 1.按章Jlink 驱动…

搭建一个简单的负载均衡

前言&#xff1a; 负载均衡是互联网系统架构中必不可少的一个技术&#xff0c;通过负载均衡&#xff0c;可以将高并发的用户请求分发到多台应用服务器组成的一个服务器集群上&#xff0c;利用更多的服务器资源处理高并发下的计算压力。 早期负载均衡的实现&#xff0c;使用专…

[Linux]Linux调试器-gdb

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

HCIA实验(作业)

静态路由综合实验报告 实验目的 建立下图拓扑结构并满足下列要求&#xff1a; 除R5的环回地址固定以外&#xff0c;整个其他所有网段基于192.168.1.0/24进行合理的IP地址划分 R1–R4每个路由器存在两个环回接口&#xff0c;用于模拟连接PC网段&#xff0c;地址也在192.168.1.0…

使用服务网格提升应用和网络安全

当今的企业在正常进行经营的同时&#xff0c;也在不断地与潜在的黑客和不良行为者进行斗争。安全边界逐渐消失&#xff0c;攻击面不断扩大&#xff0c;新的攻击向量不断出现。再加上持续的疫情、全球冲突&#xff0c;难怪每天新闻里都有漏洞、黑客和攻击等内容。 云原生和微服…

低代码平台的七大误解(下)

接上一篇文章“低代码平台的三大误解&#xff08;上&#xff09;”&#xff0c;我们继续看看&#xff0c;人们对于低代码平台还有哪些误解。 误解四&#xff1a;低代码应用程序只能解决我的部分问题 无论您构建什么应用程序&#xff0c;它都必须是可以随着业务增长而扩展的可…

Linux应用编程---2.fork()函数

Linux应用编程—2.fork()函数 ​ fork()函数用来创建子进程&#xff0c;函数具体功能与使用方法一起看编程手册。Linux终端命令下输入&#xff1a;man fork&#xff0c;敲击回车键即可打开fork函数详情页。 2.1 fork()函数详情 图1 fork函数详情首先看SYNOPSIS: 图2 fork函数…

一分钟带你上手JS对象的基本用法

前言 相信大家对 JavaScript 中的对象都不陌生&#xff0c;而且我们几乎每天都在使用它&#xff0c;那你对对象的认识有多少呢&#xff1f;本章就带大家一起出浅入深的了解 JavaScript 中的对象。 一、什么是对象&#xff1f; 到底什么是对象呢&#xff1f;大多数人可能都会脱…

生物信息学——基础篇——一至三代测序技术

生物信息学 生物信息学——基础篇——一至三代测序技术 文章目录生物信息学一、一代测序二、二代测序三、三代测序四、总结一、一代测序 概述&#xff1a;一代测序&#xff08;又称Sanger测序&#xff09;。 原理&#xff1a;Sanger测序利用一类特殊的核昔酸&#xff0c;即dd…

imx6ull内核添加exfat,并自动开机加载

下载地址&#xff1a;https://github.com/dorimanx/exfat-nofuse.git 方式一&#xff0c;移植到内核&#xff0c;通过内核开启 1、下载exfat源码&#xff08;将源码目录设置成exfat方便修改&#xff09;&#xff0c;将其放到内核的fs目录下 2、修改fs目录下的Kconfig文件 3、…