前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mybatis源码(1) -- 如何在Spring中驰骋的

mybatis源码(1) -- 如何在Spring中驰骋的

作者头像
alexqdjay
发布2018-05-11 13:58:27
9110
发布2018-05-11 13:58:27
举报
文章被收录于专栏:alexqdjayalexqdjay

mybatis作为持久层流行框架已经被很多产品使用,当然为了接入Spring这个业内的另一个流行框架,mybatis还是做了些事,通过分析除了明白支持Spring的机制原理还了解Spring对持久层接入留了那些口。

使用

代码语言:javascript
复制
    <!-- 配置SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:**/dao/**/*.xml"/>
        <property name="configLocation" value="classpath:spring/mybatis-config.xml" />
    </bean>

    <!-- 扫描Dao类工具 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.**.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
代码语言:javascript
复制
public interface UserDao {
  void save(User user);
  User query(String id);
}

@Service
public class UserService {
  @Autowired
  private UserDao userDao;

  public void saveUser(User user) {
    userDao.save(user);
  }

}
代码语言:javascript
复制
<mapper namespace="com.ss.dao.UserDao">
  <select id="save" resultType="com.ss.dto.User">
    select .... 
  </select>
</mapper>

这里对应 UserDao 的 sqlmap就省略具体sql了。

XML定义完两个Bean后,可见日常开发只需要添加Dao接口,以及对应的sqlmap,然后在调用的Service中就可以自动注入,非常方便。

自动注入机制原理

1. SqlSessionFactoryBean

代码语言:javascript
复制
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>

SqlSessionFactoryBean 用于生产 SqlSessionFactory 的 FactoryBean

那么,SqlSessionFactory 有什么用? 如果没有使用Spring,那么我们怎么使用mybatis,如下:

代码语言:javascript
复制
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

原来是用于openSession() 返回 SqlSession 的。

2. MapperScannerConfigurer

代码语言:javascript
复制
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor

从实现的接口可以看出,多半用于处理 BeanDefinition 的,该接口需要实现下面的方法。

代码语言:javascript
复制
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)

MapperScannerConfigurer 的实现源码

代码语言:javascript
复制
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if(this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        
        // 省略部分 code ...

        // 最主要是下面的 scan 定义的basePackage
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

即扫描配置basePackage中dao接口类,然后对扫描结果 beanDefinitions 进行处理

代码语言:javascript
复制
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 调用父类进行扫描
        Set beanDefinitions = super.doScan(basePackages);
        if(beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in \'" + Arrays.toString(basePackages) + "\' package. Please check your configuration.");
        } else {
            // 对结果进行处理
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

所以,主要的逻辑都集中在 processBeanDefinitions() 这个方法

代码语言:javascript
复制
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    Iterator i$ = beanDefinitions.iterator();

    while(i$.hasNext()) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)i$.next();
        GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Creating MapperFactoryBean with name \'" + holder.getBeanName() + "\' and \'" + definition.getBeanClassName() + "\' mapperInterface");
        }
        
        // 这边使用的招数叫【偷梁换柱】, 将原来的 class 换成了 MapperFactoryBean, 还给它设置了需要的参数
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        definition.getPropertyValues().add("addToConfig", Boolean.valueOf(this.addToConfig));
        
        // 下面对 SqlSessionFactory 的引入处理
        // 相关 code 省略
    }
}

就是说最终通过 MapperFactoryBean 的 getObject() 来生成Dao接口的实例,然后Service中 @Autowired 获取到的就是该实例,至于为什么?因为实现 FactoryBean 接口。

代码语言:javascript
复制
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

    //其他 code ...

    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

    public Class<T> getObjectType() {
        return this.mapperInterface;
    }

    public boolean isSingleton() {
        return true;
    }

}

3. 总结

到这里自动注入的秘密已经揭开,然后它怎么通过

代码语言:javascript
复制
this.getSqlSession().getMapper(this.mapperInterface)

来返回代理对象的,基本上也就是动态代理那套东西,感兴趣的可以翻阅 mybatis源码分析之mapper动态代理  写得蛮详细的。

事务管理

说到持久层,那么事务管理不能避免,mybatis是怎么样跟Spring的事务管理结合到天衣无缝的,下面分析。

1. SqlSessionTemplate

上一章中提到,方法

代码语言:javascript
复制
    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

这里 getSqlSession() 还是我们所知道的那个 DefaultSqlSession 么,显然不是了

代码语言:javascript
复制
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if(!this.externalSqlSession) {
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }

    }

当 set 进时已经被包装了,所以真实都是调用 SqlSessionTemplate 的方法,SqlSessionTemplate 的密码都藏在它的构造方法中:

代码语言:javascript
复制
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;

    // 生成了一个 SqlSession 的代理,调用 SqlSessionTemplate 的方法其实都转调了 sqlSessionProxy 这个代理
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

public int insert(String statement) {
    return this.sqlSessionProxy.insert(statement);
}

既然是动态代理,那么处理逻辑就都在那个 InvocationHandler  的实现

代码语言:javascript
复制
private class SqlSessionInterceptor implements InvocationHandler {

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

        Object unwrapped;
        try {
            // 调用 sqlSession 执行方法
            Object t = method.invoke(sqlSession, args);
            if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }

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

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

        }

        return unwrapped;
    }
}

简化成

代码语言:javascript
复制
private class SqlSessionInterceptor implements InvocationHandler {
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取 sqlSession
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        // 真实执行方法 
        Object t = method.invoke(sqlSession, args);

        // close sqlSession
        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

        return unwrapped;
    }
}

这样就是典型的 around 结构。

这时,如果没有事务管理框架的话,那么必然需要自己向 DataSource 获取 connection,然后根据需要开启事务,最后再commit 事务。

但是,如果有事务管理框架的话,就需要向框架获取 connection,因为这时事务可能已经被框架生成的代理开启了。

mybatis 也遵照这种处理方式,跟踪源码。

2. SqlSessionUtils.getSqlSession()

代码语言:javascript
复制
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    // TransactionSynchronizationManager.getResource 的源码就不贴了,本质就是 ThreadLocal 缓存了一个sessionFactorty
    // 为key的, sessionHolder 为value的map, 这样每个线程都有自己的sqlsession,执行时没有线程同步问题
    // sqlsession 本身线程不安全 
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if(session != null) {
        return session;
    } else {
        if(LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }
        // 如果没有缓存就open一个,然后 regist,即缓存起来
        session = sessionFactory.openSession(executorType);
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

3. sessionFactory.openSession()

代码语言:javascript
复制
public SqlSession openSession(ExecutorType execType) {
    return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment e = this.configuration.getEnvironment();
        
        // 从 Environment 获取 TransactionFactory 
        // transactionFactory.newTransaction 开启新事务
        // TransactionFactory 接口有3个实现类
        // 1. JdbcTransactionFactory
        // 2. SpringManagedTransactionFactory
        // 3. ManagedTransactionFactory
        // 当独立使用时使用的是1,当与spring结合时使用的是3(后面说明这个)
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
        tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
        Executor executor = this.configuration.newExecutor(tx, execType);
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

4. SpringManagedTransactionFactory

代码语言:javascript
复制
public class SpringManagedTransactionFactory implements TransactionFactory {

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

// 简化省略的代码
public class SpringManagedTransaction implements Transaction {
    public Connection getConnection() throws SQLException {
        if(this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    private void openConnection() throws SQLException {
        // DataSourceUtils.getConnection 是获取当前线程的conn,也是ThreadLocal方式
        // key为ds,value就是conn
        // 如果事务框架已经开启事务,那么当前线程已经换成conn返回即可,没有的话通过ds获取一个再缓存
        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");
        }

    }
}

总结

到这已经明了,最终 SpringManagedTransaction 控制着 openConnection 大权,而它索要过来的conn是来自“官方”(spring)事务管理的conn。

这时,不管声明式事务和编程式事务只要遵守spring事务管理的都能起作用。

补充

上面遗留一个问题:SpringManagedTransactionFactory 是何时被装配进 Evn中的?

这个要回到 SqlSessionFactoryBean

代码语言:javascript
复制
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 
    // ... 解析 XML配置,如cofnig mybatis-config.xml 及 mapperLocations 等
    // 代码 省略

    // 就是这里将 SpringManagedTransactionFactory 配置到 Env 中
    if(this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    // ...
}

close 关闭

回到上面 1 的最后 SqlSessionUtils.closeSqlSession(),是不是真的将sqlSession关闭?sqlSession的关闭会把事务关闭或者连接关闭么?

代码语言:javascript
复制
    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        Assert.notNull(session, "No SqlSession specified");
        Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
        SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
        // 只有 holder 丢失或者 session 不一致才会真实 session.close
        // 其他情况只是 holder.released() 将引用数减一
        if(holder != null && holder.getSqlSession() == session) {
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
            }

            holder.released();
        } else {
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
            }

            session.close();
        }

    }

session.close() , DefaultSqlSession 的源码:

代码语言:javascript
复制
public void close() {
    try {
        this.executor.close(this.isCommitOrRollbackRequired(false));
        this.dirty = false;
    } finally {
        ErrorContext.instance().reset();
    }

}

// this.executor.close 代码:
public void close(boolean forceRollback) {
    try {
        try {
            this.rollback(forceRollback);
        } finally {
            if(this.transaction != null) {
                // 调用了 tx 的close
                this.transaction.close();
            }

        }
    } catch (SQLException var11) {
        log.warn("Unexpected exception on closing transaction.  Cause: " + var11);
    } finally {
        this.transaction = null;
        this.deferredLoads = null;
        this.localCache = null;
        this.localOutputParameterCache = null;
        this.closed = true;
    }

}

// 这里的tx 是 SpringManagedTransaction, 上面已经分析
public void close() throws SQLException {
    DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}

// 中间代码省略,最终代码
public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
    // 当 holder没有丢失,conn 还是一致时,并不会真正的release
    if(con != null) {
        if(dataSource != null) {
            ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
            if(conHolder != null && connectionEquals(conHolder, con)) {
                conHolder.released();
                return;
            }
        }

        logger.debug("Returning JDBC Connection to DataSource");
        doCloseConnection(con, dataSource);
    }
}

可见,mybatis的close在一般情况下并不会真正去调用 conn.close(), 而是拖给 SpringManagedTransaction  去处理判断是否真实close,还是holder.released()。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用
  • 自动注入机制原理
    • 1. SqlSessionFactoryBean
      • 2. MapperScannerConfigurer
        • 3. 总结
        • 事务管理
          • 1. SqlSessionTemplate
            • 2. SqlSessionUtils.getSqlSession()
              • 3. sessionFactory.openSession()
                • 4. SpringManagedTransactionFactory
                  • 总结
                  • 补充
                    • close 关闭
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档