zoukankan      html  css  js  c++  java
  • 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实例

  • 相关阅读:
    关于aspx消除源代码的第一行空行(一步搞定!!!)
    考虑
    一段时间没来园子了
    随便写写
    自考群网站功能说明
    打开一个别人的代码遇到的问题
    i7 4790 z97ar ssd 固态硬盘 装机的一些经历
    我编程遇到的问题,解决方法与大家分享
    学习,学习,还是学习
    准备考试
  • 原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11286130.html
Copyright © 2011-2022 走看看