Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下

<!--会话工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

<!--spring事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>

<!--使用注释事务 -->
<tx:annotation-driven  transaction-manager="transactionManager" />

看到没,sqlSessionFactory中配置了dataSource,transactionManager也配置了dataSource,我们来回忆一下SqlSessionFactoryBean这个类

 1 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 2
 3     // 配置类
 4    Configuration configuration;
 5     // 解析mybatis-Config.xml文件,
 6     // 将相关配置信息保存到configuration
 7    XMLConfigBuilder xmlConfigBuilder = null;
 8    if (this.configuration != null) {
 9      configuration = this.configuration;
10      if (configuration.getVariables() == null) {
11        configuration.setVariables(this.configurationProperties);
12      } else if (this.configurationProperties != null) {
13        configuration.getVariables().putAll(this.configurationProperties);
14      }
15     //资源文件不为空
16    } else if (this.configLocation != null) {
17      //根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象
18      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
19      //将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性
20      configuration = xmlConfigBuilder.getConfiguration();
21    }
22
23     //略....
24
25    if (xmlConfigBuilder != null) {
26      try {
27        //解析mybatis-Config.xml文件,并将相关配置信息保存到configuration
28        xmlConfigBuilder.parse();
29        if (LOGGER.isDebugEnabled()) {
30          LOGGER.debug("Parsed configuration file: ‘" + this.configLocation + "‘");
31        }
32      } catch (Exception ex) {
33        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
34      }
35    }
36
37    if (this.transactionFactory == null) {
38      //事务默认采用SpringManagedTransaction,这一块非常重要
39      this.transactionFactory = new SpringManagedTransactionFactory();
40    }
41     // 为sqlSessionFactory绑定事务管理器和数据源
42     // 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL
43    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
44     // 解析mapper.xml
45    if (!isEmpty(this.mapperLocations)) {
46      for (Resource mapperLocation : this.mapperLocations) {
47        if (mapperLocation == null) {
48          continue;
49        }
50        try {
51          // 解析mapper.xml文件,并注册到configuration对象的mapperRegistry
52          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
53              configuration, mapperLocation.toString(), configuration.getSqlFragments());
54          xmlMapperBuilder.parse();
55        } catch (Exception e) {
56          throw new NestedIOException("Failed to parse mapping resource: ‘" + mapperLocation + "‘", e);
57        } finally {
58          ErrorContext.instance().reset();
59        }
60
61        if (LOGGER.isDebugEnabled()) {
62          LOGGER.debug("Parsed mapper file: ‘" + mapperLocation + "‘");
63        }
64      }
65    } else {
66      if (LOGGER.isDebugEnabled()) {
67        LOGGER.debug("Property ‘mapperLocations‘ was not specified or no matching resources found");
68      }
69    }
70
71     // 将Configuration对象实例作为参数,
72     // 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例
73    return this.sqlSessionFactoryBuilder.build(configuration);
74 }

我们看第39行,Mybatis集成Spring后,默认使用的transactionFactory是SpringManagedTransactionFactory,那我们就来看看其获取Transaction的方法

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won‘t support transactions
        autoCommit = true;
      }
      //从configuration中取出environment对象
      final Environment environment = configuration.getEnvironment();
      //从environment中取出TransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建Transaction
      final Transaction tx = transactionFactory.newTransaction(connection);
      //创建包含事务操作的执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //构建包含执行器的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    //这里返回SpringManagedTransactionFactory
    return environment.getTransactionFactory();
}

@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    //创建SpringManagedTransaction
    return new SpringManagedTransaction(dataSource);
}

SpringManagedTransaction

也就是说mybatis的执行事务的事务管理器就切换成了SpringManagedTransaction,下面我们再去看看SpringManagedTransactionFactory类的源码:

public class SpringManagedTransaction implements Transaction {
    private static final Log LOGGER = LogFactory.getLog(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 {
        //通过DataSourceUtils获取connection,这里和JdbcTransaction不一样
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("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) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
            }
            //通过connection提交,这里和JdbcTransaction一样
            this.connection.commit();
        }

    }

    public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }
            //通过connection回滚,这里和JdbcTransaction一样
            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;
    }
}

org.springframework.jdbc.datasource.DataSourceUtils#getConnection

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
    try {
        return doGetConnection(dataSource);
    }
    catch (SQLException ex) {
        throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
    }
}

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");
    //TransactionSynchronizationManager重点!!!有没有很熟悉的感觉??
    //还记得我们前面Spring事务源码的分析吗?@Transaction会创建Connection,并放入ThreadLocal中
    //这里从ThreadLocal中获取ConnectionHolder
    ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
        logger.debug("Fetching JDBC Connection from DataSource");
        //如果没有使用@Transaction,那调用Mapper接口方法时,也是通过Spring的方法获取Connection
        Connection con = fetchConnection(dataSource);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            logger.debug("Registering transaction synchronization for JDBC Connection");
            ConnectionHolder holderToUse = conHolder;
            if (conHolder == null) {
                holderToUse = new ConnectionHolder(con);
            } else {
                conHolder.setConnection(con);
            }

            holderToUse.requested();
            TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
            holderToUse.setSynchronizedWithTransaction(true);
            if (holderToUse != conHolder) {
                //将获取到的ConnectionHolder放入ThreadLocal中,那么当前线程调用下一个接口,下一个接口使用了Spring事务,那Spring事务也可以直接取到Mybatis创建的Connection
                //通过ThreadLocal保证了同一线程中Spring事务使用的Connection和Mapper代理类使用的Connection是同一个
                TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
            }
        }

        return con;
    } else {
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(fetchConnection(dataSource));
        }

        //所以如果我们业务代码使用了@Transaction注解,在Spring中就已经通过dataSource创建了一个Connection并放入ThreadLocal中
        //那么当Mapper代理对象调用方法时,通过SqlSession的SpringManagedTransaction获取连接时,就直接获取到了当前线程中Spring事务创建的Connection并返回
        return conHolder.getConnection();
    }
}

想看怎么获取connHolder

org.springframework.transaction.support.TransactionSynchronizationManager#getResource

//保存数据库连接的ThreadLocal
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    //获取ConnectionHolder
    Object value = doGetResource(actualKey);
    ....
    return value;
}

@Nullable
private static Object doGetResource(Object actualKey) {
    /**
     * 从threadlocal <Map<Object, Object>>中取出来当前线程绑定的map
     * map里面存的是<dataSource,ConnectionHolder>
     */
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    //map中取出来对应dataSource的ConnectionHolder
    Object value = map.get(actualKey);
    // Transparently remove ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

我们看到直接从ThreadLocal中取出来的conn,而spring自己的事务也是操作的这个ThreadLocal中的conn来进行事务的开启和回滚,由此我们知道了在同一线程中Spring事务中的Connection和Mybaits中Mapper代理对象中操作数据库的Connection是同一个,当取出来的conn为空时候,调用org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取,然后把从数据源取出来的连接返回

private static Connection fetchConnection(DataSource dataSource) throws SQLException {
    //从数据源取出来conn
    Connection con = dataSource.getConnection();
    if (con == null) {
        throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
    }
    return con;
}

我们再来回顾一下上篇文章中的SqlSessionInterceptor

 1 private class SqlSessionInterceptor implements InvocationHandler {
 2     private SqlSessionInterceptor() {
 3     }
 4
 5     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 6         SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
 7
 8         Object unwrapped;
 9         try {
10             Object result = method.invoke(sqlSession, args);
11             // 如果当前操作没有在一个Spring事务中,则手动commit一下
12             // 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit
13             // 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个Mapper的方法,sqlSession都提交了
14             // sqlSession提交,会清空一级缓存
15             if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
16                 sqlSession.commit(true);
17             }
18
19             unwrapped = result;
20         } catch (Throwable var11) {
21             unwrapped = ExceptionUtil.unwrapThrowable(var11);
22             if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
23                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
24                 sqlSession = null;
25                 Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
26                 if (translated != null) {
27                     unwrapped = translated;
28                 }
29             }
30
31             throw (Throwable)unwrapped;
32         } finally {
33             if (sqlSession != null) {
34                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
35             }
36
37         }
38         return unwrapped;
39     }
40 }

看第15和16行,如果我们没有使用@Transation,Mapper方法执行完后,sqlSession将会提交,也就是说通过org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取到的Connection将会commit,相当于Connection是自动提交的,也就是说如果不使用@Transation,Mybatis将没有事务可言。

如果使用了@Transation呢?那在调用Mapper代理类的方法之前就已经通过Spring的事务生成了Connection并放入ThreadLocal,并且设置事务不自动提交,当前线程多个Mapper代理对象调用数据库操作方法时,将从ThreadLocal获取Spring创建的connection,在所有的Mapper方法调用完后,Spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了

还有文章开头的问题,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?

因为Spring事务在没调用Mapper方法之前就需要开一个Connection,并设置事务不自动提交,那么transactionManager中自然要配置dataSource。那如果我们的Service没有用到Spring事务呢,难道就不需要获取数据库连接了吗?当然不是,此时通过SpringManagedTransaction调用org.springframework.jdbc.datasource.DataSourceUtils#getConnection#fetchConnection方法获取,并将dataSource作为参数传进去,实际上获取的Connection都是通过dataSource来获取的。

原文地址:https://www.cnblogs.com/java-chen-hao/p/11839993.html

时间: 2024-11-05 11:23:42

Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?的相关文章

Spring事务源码解析(二)获取增强

在上一篇文章@EnableTransactionManagement注解解析中,我们搭建了源码阅读的环境,以及解析了开启Spring事务功能的注解@EnableTransactionManagement的实现逻辑 在进行接下来的源码解析之前我想大家应该知道,当我们使用传统的jdbc应用事务的时候是不是做了如下操作: 开启事务 save.update.delete等操作 出现异常进行回滚 正常情况提交事务 而在Spring中我们好像只需要关心第三步,也就是我们的业务,而其他的操作都不需要关心.那么

Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析

像Mybatis.Hibernate这样的ORM框架,封装了JDBC的大部分操作,极大的简化了我们对数据库的操作. 在实际项目中,我们发现在一个事务中查询同样的语句两次的时候,第二次没有进行数据库查询,直接返回了结果,实际这种情况我们就可以称为缓存. Mybatis的缓存级别 一级缓存 MyBatis的一级查询缓存(也叫作本地缓存)是基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是SqlSession,myBat

RxJava2 源码解析(二)

转载请标明出处: http://blog.csdn.net/zxt0601/article/details/61637439 本文出自:[张旭童的博客](http://blog.csdn.net/zxt0601) 概述 承接上一篇RxJava2 源码解析(一), 本系列我们的目的: 知道源头(Observable)是如何将数据发送出去的. 知道终点(Observer)是如何接收到数据的. 何时将源头和终点关联起来的 知道线程调度是怎么实现的 知道操作符是怎么实现的 本篇计划讲解一下4,5. Rx

underscore.js源码解析(二)

前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文. 没看过上一篇的可以猛戳这里:underscore.js源码解析(一) underscore.js源码GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js 本文解析的underscore.js版本是1.8.3 _.each 1

OpenStack Neutron LoadBalance源码解析(二)

声明: 本博客欢迎转载,但请保留原作者信息,并请注明出处:http://write.blog.csdn.net/! 作者:林凯 团队:华为杭州OpenStack团队 在Neutron LoadBalance源码解析(一)中,我们已经了解租户在创建pool.member.healthmonitor和vip的时候,代码会调用HaproxyNSDriver中的create_xxx函数,那么当租户使用pool创建vip时,即代码调用HaproxyNSDriver中的create_vip函数时,Neutr

BroadcastReceiver源码解析(二)

广播注册部分见BroadcastReceiver源码解析(一) 4,BroadcastReceiver之源码分析 4.1,动态注册过程源码分析 在Activity中动态注册广播时,在注册方法之前其实省略了Context,也就是实际上调用的是Context. registerReceiver().Context是一个抽象类,它是Client端和AMS,WMS等系统服务进行通信的接口,Activity.Service和Application都是继承它的子类.Context的实现类是ContextIm

SDWebImage源码解析(二)

源码来源: https://github.com/rs/SDWebImage 版本: 3.7 SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.它具有以下功能: 提供UIImageView的一个分类,以支持网络图片的加载与缓存管理 一个异步的图片加载器 一个异步的内存+磁盘图片缓存 支持GIF图片 支持WebP图片 后台图片解压缩处理 确保同一个URL的图片不被下载多次 确保虚假的URL不会被反复加载 确保下载及缓存时,主

Android FM模块学习之四源码解析(二)

上一章我们了解了FM主activity:FMRadio.java,若没查看的,请打开链接Android FM模块学习之四源码解析(一) 查看fmradio.java源码注释.接下来我们来看看FM重要的一个类:FMRadioService.java 由上一章我们已经知道,打开FM时,在OnStart函数中会bindToService来开启服务, public boolean bindToService(Context context, ServiceConnection callback) { L

浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty

原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为了解析SQL脚本中的参数,根据给定的开始标记与结束标记来进行参数的定位获取,然后右标记处理器进行参数处理,再然后将处理过后的参数再组装回SQL脚本中. 如此一来,解析的目的就是为了处理参数. 这一篇看看binding绑定模块. 2.binding模块 binding模块位于org.apache.ib