SqlSessionManager自己實現了SqlSessionFactory, SqlSession兩個接口,因此自己可以構建Sqlsession和使用Sqlsesion聲明的CURD相關的查詢方法。SqlSessionManager不能經過構造器進行實例化,只能經過newInstance()方法進行實例化,事實上,在newInstance()方法中調用了SqlSessionFactoryBuilder.build()方法實例化了一個DefaultSqlsessionFactory。而後再使用構造器進行實例化。java
SqlSessionManager內部維護着三個屬性sqlSessionFactory,sqlSessionProxy,localSqlSession。其中sqlSessionFactory的做用就是參與對sqlSessionProxy的構建,從下面的構造器代碼就知道,sqlSessionProxy使用了動態代理模式建立了SqlSession的代理對象。在之後操做CURD相關方法時候,都會委託給這個代理對象。最後一個屬性localSqlSession由一個本地線程進行維護,這樣保證了併發安全,除了此功能外,這個屬性體現了「納管」這一律念,在執行完startManagedSession()方法後,會將對應線程的SqlSession進行管理,在使用代理模式建立代理實例時,再也不經過opensession()方法進行獲取,而是直接從本地線程中進行獲取。除了這一點差別以外, SqlSessionManager還提供了多個額外的方法用於操做本地線程中的SqlSession對象。以下列方法:mysql
SqlSessionManager構造器代碼和newInstance()方法sql
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()方法拿到新實例。apache
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,姓名,年紀三個值。安全
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
二者的區別和聯繫session
二者的查詢方式都是經過JDK的動態代理實現,只不過在sqlSessionProxy內部一個使用了本地線程獲取SqlSession實例,一個使用openSession()方法獲取SqlSession實例。使用debug觀察使用了非manager.startManagedSession();所使用的SqlSession對象,能夠看到localSqlSession中取到的值爲NULL,SqlSession必須經過openSessiom()方法獲取實例。mybatis
若是啓用了startManagedSession()方法,則會從localSqlSession中獲取SqlSession實例,能夠看到兩次的id號都是71,也就是兩次查詢都是同一個實例。併發