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

相關文章
相關標籤/搜索