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

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

使用

<!-- 配置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>
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);
  }

}
<mapper namespace="com.ss.dao.UserDao">
  <select id="save" resultType="com.ss.dto.User">
    select .... 
  </select>
</mapper>

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

XML定义完两个Bean后,可见平常开发只须要添加Dao接口,以及对应的sqlmap,而后在调用的Service中就能够自动注入,很是方便。sql

自动注入机制原理

1. SqlSessionFactoryBean

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>

 SqlSessionFactoryBean 用于生产 SqlSessionFactory 的 FactoryBean编程

那么,SqlSessionFactory 有什么用? 若是没有使用Spring,那么咱们怎么使用mybatis,以下:缓存

SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

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

2. MapperScannerConfigurer

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor

从实现的接口能够看出,多半用于处理 BeanDefinition 的,该接口须要实现下面的方法。session

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)

MapperScannerConfigurer 的实现源码mybatis

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 进行处理app

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() 这个方法框架

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 接口。

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. 总结

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

this.getSqlSession().getMapper(this.mapperInterface)

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

事务管理

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

1. SqlSessionTemplate

上一章中提到,方法

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

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

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

    }

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

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  的实现

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

简化成

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()

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()

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

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

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的关闭会把事务关闭或者链接关闭么?

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 的源码:

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()。

相关文章
相关标签/搜索