Mybatis源码分析:SqlSessionManager

SqlSessionManager

   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

  • isManagedSessionStarted() 判断是否受到SqlSessionManager的管制,其本质是判断SqlSeesion对象是否在本地线程对象中
  • getConnection() 从本地线程中获取链接对象
  • close(); 关闭线程所属的SqlSession对象,并将SqlSession对象置为NULL

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,姓名,年纪三个值。安全

  • 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

 

二者的区别和联系session

   二者的查询方式都是经过JDK的动态代理实现,只不过在sqlSessionProxy内部一个使用了本地线程获取SqlSession实例,一个使用openSession()方法获取SqlSession实例。使用debug观察使用了非manager.startManagedSession();所使用的SqlSession对象,能够看到localSqlSession中取到的值为NULL,SqlSession必须经过openSessiom()方法获取实例。mybatis

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

 

若是启用了startManagedSession()方法,则会从localSqlSession中获取SqlSession实例,能够看到两次的id号都是71,也就是两次查询都是同一个实例。并发

  • 使用startManagedSession()生成SqlSession实例

相关文章
相关标签/搜索