Mybatis源碼分析:SqlSessionManager


SqlSessionManager

   SqlSessionManager本身實現了SqlSessionFactory, SqlSession兩個接口,所以本身能夠構建Sqlsession和使用Sqlsesion聲明的CURD相關的查詢方法。SqlSessionManager不能通過構造器進行實例化,只能通過newInstance()方法進行實例化,事實上,在newInstance()方法中調用了SqlSessionFactoryBuilder.build()方法實例化了一個DefaultSqlsessionFactory。然后再使用構造器進行實例化。

   SqlSessionManager內部維護着三個屬性sqlSessionFactory,sqlSessionProxy,localSqlSession。其中sqlSessionFactory的作用就是參與對sqlSessionProxy的構建,從下面的構造器代碼就知道,sqlSessionProxy使用了動態代理模式創建了SqlSession的代理對象。在以后操作CURD相關方法時候,都會委托給這個代理對象。最后一個屬性localSqlSession由一個本地線程進行維護,這樣保證了並發安全,除了此功能外,這個屬性體現了“納管”這一概念,在執行完startManagedSession()方法后,會將對應線程的SqlSession進行管理,在使用代理模式創建代理實例時,不再通過opensession()方法進行獲取,而是直接從本地線程中進行獲取。除了這一點差異之外, SqlSessionManager還提供了多個額外的方法用於操作本地線程中的SqlSession對象。如下列方法:

  • isManagedSessionStarted() 判斷是否受到SqlSessionManager的管制,其本質是判斷SqlSeesion對象是否在本地線程對象中
  • getConnection() 從本地線程中獲取連接對象
  • close(); 關閉線程所屬的SqlSession對象,並將SqlSession對象置為NULL

SqlSessionManager構造器代碼和newInstance()方法

 1   private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
 2     this.sqlSessionFactory = sqlSessionFactory;
 3     this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
 4         SqlSessionFactory.class.getClassLoader(),
 5         new Class[]{SqlSession.class},
 6         new SqlSessionInterceptor());
 7   }
 8   //通過InputStream進行實例化
 9   public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {
10     return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));
11   }
12 //通過SqlSessionFactory 進行實例化
13   public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
14     return new SqlSessionManager(sqlSessionFactory);
15   }
16 //通過Reader進行實例化
17   public static SqlSessionManager newInstance(Reader reader, String environment) {
18     return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));

 

   我們知道使用JDK動態代理必須實現InvocationHandler接口並且覆寫invoke()方法,SqlSessionManager構造器在實例化代理類時SqlSessionInterceptor類就實現了InvocationHandler接口,觀察invoke()方法。invoke第一行代碼嘗試從本地線程中拿到Sqlsession,但如果未調用startManagedSession()方法,那么永遠拿到的都是null值,也就是說,invoke每次通過openSession()方法拿到新實例。

 1  private class SqlSessionInterceptor implements InvocationHandler {
 2     public SqlSessionInterceptor() {
 3         // Prevent Synthetic Access
 4     }
 5 
 6     @Override
 7     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 8         //從本地線程中獲取獲取SqlSession,使用SqlSessionManager時並不會向本地線程中放置Sqlsession,此時sqlsession實例為null,除非啟用了startManagedSession()
 9       final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
10       if (sqlSession != null) {
11         try {
12           return method.invoke(sqlSession, args);
13         } catch (Throwable t) {
14           throw ExceptionUtil.unwrapThrowable(t);
15         }
16       } else {
17           //通過sqlSessionFactory獲取SqlSession
18         final SqlSession autoSqlSession = openSession();
19         try {
20           final Object result = method.invoke(autoSqlSession, args);
21           autoSqlSession.commit();
22           return result;
23         } catch (Throwable t) {
24           autoSqlSession.rollback();
25           throw ExceptionUtil.unwrapThrowable(t);
26         } finally {
27           autoSqlSession.close();
28         }
29       }
30     }
31   }

 

運行一個實例

對比使用了startManagedSession()方法的SqlSessionManager和直接newInstance()的測試有什么不同。首先配置一個SqlMapper,存在一個list方法,查詢學生表中的學生id,姓名,年紀三個值。

  • SqlMapper接口
 1 package com.zzz.mybatis.mapper;
 2 
 3 import java.util.List;
 4 import java.util.Map;
 5 import org.apache.ibatis.annotations.Flush;
 6 import org.apache.ibatis.annotations.MapKey;
 7 import org.apache.ibatis.annotations.Select;
 8 
 9 public interface SqlMapper {
10     @Select("select id,name,age from student")
11     public List<Map<String, Object>> list();
12 
13     @Flush
14     public void flush();
15 
16     @MapKey(value="id")
17     @Select("select id,name,age from student")
18     public Map<String,Map<String,String>> listByMapkey();
19 }

 

  • 單元測試類
 1 package com.zzz.mybatis.service;
 2 
 3 import org.apache.ibatis.session.SqlSessionManager;
 4 import org.junit.Test;
 5 
 6 public class SqlSessionManagerTest {
 7     @Test
 8     public void SqlSessionManagerByNewInstance() {
 9         SqlSessionManager manager=SqlSessionManager.newInstance(getClass().getResourceAsStream("/mybatis-config.xml"));
10         Object rs=manager.selectOne("com.zzz.mybatis.mapper.SqlMapper.list");
11         System.out.println(rs.toString());
12     }
13 
14     @Test
15     public void SqlSessionManagerByLocalThread() {
16         SqlSessionManager manager=SqlSessionManager.newInstance(getClass().getResourceAsStream("/mybatis-config.xml"));
17         manager.startManagedSession();
18         Object rs=manager.selectOne("com.zzz.mybatis.mapper.SqlMapper.list");
19         System.out.println(rs.toString());
20         rs=manager.selectOne("com.zzz.mybatis.mapper.SqlMapper.list");
21         System.out.println(rs.toString());
22     }
23 
24 }

 

  • 運行結果
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e853265]
==>  Preparing: select id,name,age from student 
==> Parameters: 
<==    Columns: id, name, age
<==        Row: 1, zhangsan, 22
<==      Total: 1
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@57e1b0c]
==>  Preparing: select id,name,age from student 
==> Parameters: 
<==    Columns: id, name, age
<==        Row: 1, zhangsan, 22
<==      Total: 1

 

兩者的區別和聯系

   兩者的查詢方式都是通過JDK的動態代理實現,只不過在sqlSessionProxy內部一個使用了本地線程獲取SqlSession實例,一個使用openSession()方法獲取SqlSession實例。使用debug觀察使用了非manager.startManagedSession();所使用的SqlSession對象,可以看到localSqlSession中取到的值為NULL,SqlSession必須通過openSessiom()方法獲取實例。

  • 使用非startManagedSession()生成SqlSession實例

 

如果啟用了startManagedSession()方法,則會從localSqlSession中獲取SqlSession實例,可以看到兩次的id號都是71,也就是兩次查詢都是同一個實例。

  • 使用startManagedSession()生成SqlSession實例


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM