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实例

  • 相关阅读:
    PHP 大小写转换、首字母大写、每个单词首字母大写转换相关函数
    【论文学习4】BiSample: Bidirectional Sampling for Handling Missing Data with Local Differential Privacy
    【论文学习3】Local Differential Privacy for Deep Learning
    【论文学习2】 Differential Privacy Reinforcement Learning
    深度学习中的优化算法
    Spatial crowdsourcing
    “pip install tensorflow ”出现错误
    python或pip'不是内部或外部命令”
    pip install torch出现错误
    打不开gitHub的解决方法
  • 原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11286130.html
Copyright © 2011-2022 走看看