Mybatis原理

129 阅读12分钟

概述

计算机的基本工作就是存储和计算,而MyBatis是存储领域的利器。MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

原生JDBC 流程

  1. 与数据源建立连接
  2. 执行SQL语句
  3. 检索SQL执行结果
  4. 关闭连接
   private static void addOne() throws ClassNotFoundException, SQLException {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        //定义数据库的连接地址
        String url = "jdbc:mysql://localhost:3306/day02";
        //定义用户名
        String userName = "root";
        //定义密码
        String passWord = "123456";
        Connection con = DriverManager.getConnection(url, userName, passWord);
        //3.获得执行sql语句的对象
        Statement statement = con.createStatement();

        //定义sql语句
        String sql = "insert into scores(sid,score,sname) values(1,100,'柳岩')";
        //Statement对象调用exexuteUpdate方法执行sql语句,获取结果
        int result = statement.executeUpdate(sql);

        if (result > 0){
            System.out.println("添加一条记录成功");
        }else{
            System.out.println("添加一条记录失败");
        }
        //关闭资源
        statement.close();
        con.close();

    }

mybatis 的核心流程

mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件中构建出SqlSessionFactory,然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。如下图所示:

image.png

JDBC 和mybatis 对比

DBC有四个核心对象:
(1)DriverManager,用于注册数据库连接
(2)Connection,与数据库连接对象
(3)Statement/PrepareStatement,操作数据库SQL语句的对象
(4)ResultSet,结果集或一张虚拟表

而MyBatis也有四大核心对象:
(1)SqlSession对象,该对象中包含了执行SQL语句的所有方法【1】。类似于JDBC里面的Connection 【2】。
(2)Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
(3)MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
(4)ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。

spring boot 整合mybatis quick start

  1. 构建spring boot 工程
  2. 引入mybatis启动件
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>
  1. 引入mysql 驱动
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.33</version>
</dependency>
  1. 引入数据源依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.27</version>
</dependency>
  1. 配置数据源
@Bean(name = "dataSource", destroyMethod = "close")
public HikariDataSource getDataSource() {
    //每隔500ms 校验连接是否超过maxLifeTime
    System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "500");
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl(env.getProperty("dataSource.jdbc.url"));
    String userName = env.getProperty("dataSource.jdbc.username");
    config.setUsername(userName);
    String pwd = CryptoUtils.decrypt(userName, env.getProperty("dataSource.jdbc.password"));
    config.setPassword(pwd);
    config.setMaximumPoolSize(Integer.parseInt(env.getProperty("dataSource.jdbc.maximumPoolSize")));
    config.addDataSourceProperty("cachePrepStmts", env.getProperty("dataSource.jdbc.cachePrepStmts"));
    config.addDataSourceProperty("prepStmtCacheSize", env.getProperty("dataSource.jdbc.prepStmtCacheSize"));
    config.addDataSourceProperty("prepStmtCacheSqlLimit", env.getProperty("dataSource.jdbc.prepStmtCacheSqlLimit"));
    config.setMaxLifetime(Long.parseLong(env.getProperty("dataSource.jdbc.maxLifeTime")));
    config.setIdleTimeout(Long.parseLong(env.getProperty("dataSource.jdbc.idleTimeoutMs")));
    config.setMinimumIdle(Integer.parseInt(env.getProperty("dataSource.jdbc.minimumIdleSize")));
    config.setValidationTimeout(Long.parseLong(env.getProperty("dataSource.jdbc.validationTimeout")));
    return new HikariDataSource(config);

}
  1. 配置mybatis 增加mybatis-config.xml 配置 \
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="cacheEnabled" value="true"/>
        <setting name="logPrefix" value="dao."/>
        <setting name="logImpl" value="SLF4J"/>
<!--        <setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>
    <typeAliases>
    </typeAliases>
</configuration>

增加mybatis 配置

#mybatis
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/*.xml
  1. 开启扫描路径
@MapperScan(basePackages = "com.zxiao.bootmybatisdemo.mapper")

image.png 8. 创建mapper接口

image.png 9. 创建mapper.xml

image.png

8、测试

image.png

mybatis 源码分析

回顾下mybatis 流程图

image.png

创建 SqlSessionFactory

@see org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

image.png

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  //数据源
  factory.setDataSource(dataSource);
  
  factory.setVfs(SpringBootVFS.class);
  
  //加载mybatis-config.xml
  if (StringUtils.hasText(this.properties.getConfigLocation())) {
    factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  }
  
  //构建 configuration
  Configuration configuration = this.properties.getConfiguration();
  if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    configuration = new Configuration();
  }
  if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
      customizer.customize(configuration);
    }
  }
  //设置configuration
  factory.setConfiguration(configuration);
  
  if (this.properties.getConfigurationProperties() != null) {
    factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  }
  if (!ObjectUtils.isEmpty(this.interceptors)) {
    factory.setPlugins(this.interceptors);
  }
  //
  if (this.databaseIdProvider != null) {
    factory.setDatabaseIdProvider(this.databaseIdProvider);
  }
  
  //设置别名
  if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
    factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  }
  
  //设置TypeHandler(java 类型和mysql 类型转换的桥梁)
  if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
    factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  }
  
  //加载mapper.xml
  if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    factory.setMapperLocations(this.properties.resolveMapperLocations());
  }

  return factory.getObject();
}

创建 SqlSession

@see org.apache.ibatis.session.SqlSessionFactory

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

SqlSessionFactory 提供创建sqlSession api
实现类有:

DefaultSqlSessionFactory

image.png

SqlSessionManager

image.png

获取SqlSession 我们以DefaultSqlSessionFactory 为例子,SqlSessionManager 据说已过时。相关代码如下:

@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

@see org.apache.ibatis.session.SqlSession SqlSession 定义了执行sql CRUD 基本操作,实现类如下

  • DefaultSqlSession(默认)
  • SqlSessionManager
  • SqlSessionTemplate(代理,spring 整合 mybatis 使用SqlSessionTemplate)

Executor

SqlSession执行增删改查都是委托给Executor完成的。

@see org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)

image.png

Executor主要完成以下几项内容:

  1. 处理缓存,包括一级缓存和二级缓存
  2. 获取数据库连接
  3. 创建Statement或者PrepareStatement对象
  4. 访问数据库执行SQL语句
  5. 处理数据库返回结果

Executor继承结构如下

image.png

  • CachingExecutor

CachingExecutor用于处理[二级缓存],如果缓存中不存在要查询的数据,那么将查询请求委托给其他的Executor。如果是执行SQL的增删改,那么CachingExecutor将清空二级缓存。
关于CachingExecutor的其他内容可以参见

  • BaseExecutor

BaseExecutor是除CachingExecutor之外,其他Executor实现类的基类。该类主要处理一级缓存。该类中属性localCache表示一级缓存。

当调用该类的查询方法时,先查看一级缓存中是否已经有数据,如果有则直接从缓存获取,如果没有调用子类的查询方法从数据库中获取。 当调用该类的update方法(mybatis将delete和insert认为是update,统一调用该类update方法)时,BaseExecutor将一级缓存清空,然后调用子类对应的增删改方法。 调用执行rollback/commit方法时,该类清空一级缓存。

  • SimpleExecutor

SimpleExecutor继承自BaseExecutor,该类比较简单。
当执行增删改查时,该类获取数据库连接,创建PrepareStatement或者Statement对象,执行[SQL语句],最后将数据库返回结果转化为设定的对象。SimpleExecutor基本是按照标注JDBC流程执行SQL语句获得返回结果。下面以doQuery方法为例

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    
    //创建StatementHandler对象
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    
    //获得数据库连接,创建Statement或者PrepareStatement
    stmt = prepareStatement(handler, ms.getStatementLog());
    
    //执行SQL语句,将数据库返回结果转化为设定的对象,比如List,Map或者是POJO
    return handler.<E>query(stmt, resultHandler);
  } finally {
  
    //关闭Statement对象
    closeStatement(stmt);
  }
}
  • BatchExecutor

当执行查询时,BatchExecutor与SimpleExecutor的处理逻辑是一样的。不同的是执行更新方法(mybatis认为delete和insert都是update)。执行更新方法时,BatchExecutor不是直接执行SQL语句,而是将其放到批次里面,等到提交的时候一起执行。

public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
  final Configuration configuration = ms.getConfiguration();
  //创建Statement或者PreparedStatement对象
  final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
  
  final BoundSql boundSql = handler.getBoundSql();
  //获得原始SQL语句
  final String sql = boundSql.getSql();
  
  final Statement stmt;
  //判断当前执行的SQL是否在上一次已经执行过 
  //下面的条件表示,SQL语句相同,调用的mapper接口方法也相同
  if (sql.equals(currentSql) && ms.equals(currentStatement)) {
  //如果在上一次已经执行过,那么直接复用上一次使用的Statement或者PreparedStatement对象
    int last = statementList.size() - 1;
    stmt = statementList.get(last);
    applyTransactionTimeout(stmt);
   handler.parameterize(stmt);//fix Issues 322
   
   //batchResult用于记录批次执行结果
    BatchResult batchResult = batchResultList.get(last);
    batchResult.addParameterObject(parameterObject);
  } else {
  //下面的内容是新建Statement或者PreparedStatement对象,用于执行新的SQL语句
    Connection connection = getConnection(ms.getStatementLog());
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);    //fix Issues 322
    currentSql = sql;
    currentStatement = ms;
    statementList.add(stmt);
    batchResultList.add(new BatchResult(ms, sql, parameterObject));
  }
// handler.parameterize(stmt);

//将SQL加入批次
  handler.batch(stmt);
  
  //返回一个常量值,表示当前是批次执行
  return BATCH_UPDATE_RETURN_VALUE;
}
  • ReuseExecutor

ReuseExecutor从名字可以看出该类主要特点是复用。它复用的是Statement对象或者PreparedStatement对象。 该类中有一个属性statementMap,如下面代码所示,key是SQL语句,value是Statement对象。每次执行首先根据SQL语句查询statementMap,如果有对应的Statement对象,则直接使用该Statement对象,如果没有,则创建新的Statement对象,然后将其和SQL语句添加到statementMap,以备下次使用。

private final Map<String, Statement> statementMap = new HashMap<>();
//增删改操作
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    //创建StatementHandler对象
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    //创建或者复用Statement对象
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }
  //查询
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    //创建StatementHandler对象
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //创建或者复用Statement对象
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    //得到原始SQL语句
    String sql = boundSql.getSql();
    //检查该SQL是否在statementMap中存在
    if (hasStatementFor(sql)) {
      //得到已经存在的Statement对象
      stmt = getStatement(sql);
      //设置超时参数
      applyTransactionTimeout(stmt);
    } else {
      //如果之前没有执行过该SQL,那么获取连接
      Connection connection = getConnection(statementLog);
      //创建新的Statement对象
      stmt = handler.prepare(connection, transaction.getTimeout());
      //将SQL语句和Statement对象添加到statementMap中,以备后面复用
      putStatement(sql, stmt);
    }
    //如果stmt是PreparedStatement对象,下面的方法用于设置SQL语句的参数
    handler.parameterize(stmt);
    return stmt;
  }

  • ClosedExecutor

ClosedExecutor是ResultLoaderMap内部类,外部无法使用,调用其方法会抛出异常。

mybatis 缓存

一级缓存

mybatis 一级缓存默认开启,是session 级别的。修改 mybatis 一级缓存配置可在mybatis-config.xml增加配置

<!--        SESSION,STATEMENT-->
        <setting name="localCacheScope" value="SESSION"/>

image.png

演示一级缓存是否生效

不开启事务,一级缓存不生效

//    @Transactional
    @Override
    public List<BillingRuleLogDto> listRuleLog(Long id) {
//        return null;
        BillingRuleLogDto billingRuleLogDto = new BillingRuleLogDto();
        billingRuleLogDto.setId(id);

        List<BillingRuleLogDto> billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("first select------------");
        billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("second select--------------------");
        return billingRuleLogDtos;
    }

image.png

开启事务,一级缓存生效

    @Transactional
    @Override
    public List<BillingRuleLogDto> listRuleLog(Long id) {
//        return null;
        BillingRuleLogDto billingRuleLogDto = new BillingRuleLogDto();
        billingRuleLogDto.setId(id);

        List<BillingRuleLogDto> billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("first select------------");
        billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("second select--------------------");
        return billingRuleLogDtos;
    }

image.png

一级缓存不生效分析

spring 整合 mybatis 时,容器默认注入的sqlSession 实现类是SqlSesionTemplate @see org.mybatis.spring.support.SqlSessionDaoSupport

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }
  
  ...

SqlSessionTemplate 是SqlSession 子类,内部类SqlSessionInterceptor 实现了InvocationHandler,由此可见是个应用了代理模式,下面我们重点关注SqlSessionInterceptor

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;
  
  ...
  
@Override
public <E> List<E> selectList(String statement) {
  return this.sqlSessionProxy.<E> selectList(statement);
}


 ...
 
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
      //我们重点关注closeSqlSession 方法,因为session 被关闭了,故一级缓存失效
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

关闭seession 方法

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  notNull(session, NO_SQL_SESSION_SPECIFIED);
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 //开启事务后,session 在同一个事务中被共享
  if ((holder != null) && (holder.getSqlSession() == session)) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
    }
    holder.released();
  } else {
  //没有事务加持,直接就关闭了session
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
    }
    session.close();
  }
}

一级缓存生命周期

MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

设置statement配置中的flushCache=‘true’ 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

二级缓存

应用场景:

 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。      

  • 实现方法 通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。

  • 局限性: mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。

事务

Mybatis管理事务分为三种方式

  • JdbcTransaction:使用JDBC的事务管理机制,利用java.sql.Connection对象完成事务的提交。
  • ManagedTransaction:使用Managed的事务管理机制,mybatis自身不会去实现事务管理,而是让容器(JBOSS、WebLogic)实现对事务的管理。
  • SpringManagedTransaction:使用spring的事务管理机制,利用@Transaction注解即可实现

此篇我们重点研究SpringManagedTransaction

spring mybatis 整合后默认选择SpringManagedTransaction,源码如下 @see org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

  Configuration configuration;

  XMLConfigBuilder xmlConfigBuilder = null;
  if (this.configuration != null) {
    configuration = this.configuration;
    if (configuration.getVariables() == null) {
      configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      configuration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    }
    configuration = new Configuration();
    if (this.configurationProperties != null) {
      configuration.setVariables(this.configurationProperties);
    }
  }

  if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
  }

  if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  }

  if (this.vfs != null) {
    configuration.setVfsImpl(this.vfs);
  }

  if (hasLength(this.typeAliasesPackage)) {
    String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeAliasPackageArray) {
      configuration.getTypeAliasRegistry().registerAliases(packageToScan,
              typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
      }
    }
  }

  if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
      configuration.getTypeAliasRegistry().registerAlias(typeAlias);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered type alias: '" + typeAlias + "'");
      }
    }
  }

  if (!isEmpty(this.plugins)) {
    for (Interceptor plugin : this.plugins) {
      configuration.addInterceptor(plugin);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered plugin: '" + plugin + "'");
      }
    }
  }

  if (hasLength(this.typeHandlersPackage)) {
    String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeHandlersPackageArray) {
      configuration.getTypeHandlerRegistry().register(packageToScan);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
      }
    }
  }

  if (!isEmpty(this.typeHandlers)) {
    for (TypeHandler<?> typeHandler : this.typeHandlers) {
      configuration.getTypeHandlerRegistry().register(typeHandler);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered type handler: '" + typeHandler + "'");
      }
    }
  }

  if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
    try {
      configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  if (this.cache != null) {
    configuration.addCache(this.cache);
  }

  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();

      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
      }
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  // 注入事务管理工厂
  if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
  }
  
  ...
public class SpringManagedTransactionFactory implements TransactionFactory {
    public SpringManagedTransactionFactory() {
    }

    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new SpringManagedTransaction(dataSource);
    }

    public Transaction newTransaction(Connection conn) {
        throw new UnsupportedOperationException("New Spring transactions require a DataSource");
    }

    public void setProperties(Properties props) {
    }
}

通过在方法上加spring 事务注解即可实现事务,spring 事务注解 @Transactional 读者需要自行探索

    @Transactional
    @Override
    public List<BillingRuleLogDto> listRuleLog(Long id) {
//        return null;
        BillingRuleLogDto billingRuleLogDto = new BillingRuleLogDto();
        billingRuleLogDto.setId(id);

        List<BillingRuleLogDto> billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("first select------------");
        billingRuleLogDtos = billingRuleLogMapper.listRuleLog(billingRuleLogDto);
        log.info("second select--------------------");
        return billingRuleLogDtos;
    }

总结

Mybatis.jpg