目录

 

1.装饰器模式

2. sqlSession的创建(open)

2.1.newExecutor

3.selectOne分析

3.1.二级缓存

3.2.一级缓存

4.数据库查询核心分析(queryFromDatabase)

4.1.SimpleExecutor.doQuery

4.1.1.prepareStatement

4.1.2.预处理查询数据库

4.1.3.封装数据到对象

5.mybatis涉及的设计模式

6.mybatis整合spring

6.1.SqlSessionTemplate


1.装饰器模式

前面两篇笔记都在分析mybatis的全局配置文件的解析和原理,如果执行查询的时候mybatis的流程是怎么样的呢?在执行的流程中mybtis大量的使用到一种设计模式就是装饰器模式,就是定义了一个功能接口(执行器),然后又好几个子类去实现它,它将一些功能装饰在子类上,并且提供了缓存的执行器,这个执行器也是对功能接口进行装饰,并且由它来调度它的其他子类的装饰方法

java sql 子查询解析库 java实现sql查询分析_二级缓存

Component,抽象构件

Component是一个接口或者抽象类,是定义我们最核心的对象,也可以说是最原始的对象,比如上面的肉夹馍。

ConcreteComponent,具体构件,或者基础构件

ConcreteComponent是最核心、最原始、最基本的接口或抽象类Component

的实现,可以单独用,也可将其进行装饰,比如上面的简单肉夹馍。Decorator,装饰角色一般是一个抽象类,继承自或实现Component,在它的属性里面有一个变量指

向Component抽象构件,我觉得这是装饰器最关键的地方。

ConcreteDecorator,具体装饰角色

ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,它们可以把

基础构件装饰成新的东西,比如把一个普通肉夹馍装饰成鸡蛋里脊肉夹馍

所以我们看下mybatis中的执行器的装饰器模式的一个类继承关系图:

 

java sql 子查询解析库 java实现sql查询分析_java_02

是不是就是一个装饰器的模式,在创建Executor的时候,比如创建的是SimpleExecutor,那么如果缓存引擎是打开的,那么会创建一个CachingExecutor,将创建的SimpleExecutor通过构造丢到CachingExecutor中,当执行一个操作的时候先执行的是CachingExecutor中的方法,而真正当CachingExecutor中没有执行到想要的结果时,会调用BaseExecutor中的方法,从而调用到SimpleExecutor中的方法;所以这种设计模式就是相当于CachingExecutor是一个构建,而BaseExecutor是一个装饰角色,而下面的SimpleExecutor、BatchExecutor都是它的装饰类,当构建中不能满足功能时,通过装饰器去实现。

 

2. sqlSession的创建(open)

我们知道在mybatis中如果要执行数据库的CRUD操作,必须要先通过sessionFactory来打开也就是创建一个SqlSession,通过SqlSession才能进行数据库的CRUD操作,那么如果想要了解mybatis的整体流程,它是如何创建了SqlSession,这个要弄清楚,在前面的笔记中已经分析了SessionFactory是如何创建的,简单来说就是通过解析全局配置文件将所有的配置信息包括数据源信息解析出来放入Configuration的对象中,然后通过创建默认的session工厂也就是DefaultSqlSessionFactory,将创建好的configuration通过DefaultSqlSessionFactory传入构造方法创建的,那么SqlSession的创建也是一样的,通过DefaultSqlSessionFactory来创建打开的一个sqlSession,所以这里就先来看下是如何创建的SqlSession

 

@Override
public SqlSession openSession() {
  /**
   * 这里是创建一个默认的SqlSession=DefaultSqlSession
   * 这里没有去执行那些操作,这里主要是将前面创建的环境对象Environment获取到
   * 然后获取到事务工厂以及数据源信息,在根据这里传入的执行器类型获取一个执行器
   *  然后将这些获取到的信息封装到一个DefaultSqlSession的对象中,最后返回
   *  所以openSession()其实没有执行数据库或者说复杂的操作,就是
   *  将上一步配置文件解析的结果的一些对象获取到,饭后封装到一个DefaultSqlSession
   *  的对象中
   *  这里传入的执行器类型是:SIMPLE
   *  autoCommit:false(不自动提交)
   */
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

openSession中只有一个方法,openSessionFromDataSource,想都不用想,这个方法肯定就是去得到一个SqlSession的操作,所以进到这个方法里面去,在进这个方法之前,我们需要知道这个方法接受的参数,第一个是一个ExecutorType,这个是什么意思呢?这个就是前面设计模式将的,这里创建的要给sql执行器的类型,mybatis中有三种执行器,SIMPLE、REUSE 、BATCH,分别是简单的、复用的、批量的,所以这里需要默认一个,在前面的笔记中已经介绍了可以在全局配置中配置想要的sql执行器是如何配置的,这里就不赘述了,所以这里是要得到一个默认的执行器

java sql 子查询解析库 java实现sql查询分析_java_03

这里获取的是一个默认的;第二个和第三个参数是和事务有关的,就是事务的隔离级别和是否自动提事务的;

现在来看下表这个方法的实现:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //获取环境配置信息
    final Environment environment = configuration.getEnvironment();
    //通过环境配置信息获取事物工厂
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //通过事物工厂创建一个新的事务对象
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //通过得到的事务对象,通过configuration创建一个执行器,这个执行器就是数据库的CRUD的真正的执行者
    //这里将其创建出来
    final Executor executor = configuration.newExecutor(tx, execType);
    //最后将所有的对象封装成一个DefaultSqlSession返回
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

简单来说,这个方法的含义就是:

1.通过DefaultSqlSessionFactory拿到configuration对象,然后读取到环境信息;

2.通过环境信息拿到事务工厂;

3.通过事务工厂和执行器类型得到一个执行器;

4.然后创建一个新的DefaultSqlSession,将创建好的执行器和configuration封装到sqlSession中去。

5.最后返回这个DefaultSqlSession,完成了DefaultSqlSession的创建(open)。

 

2.1.newExecutor

 

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  //二级缓存开启的话,那么默认使用的是CachingExecutor,然后把创建的SimpleExecutor、BatchExecutor、ReuseExecutor作为属性传入,这是一种装饰模式
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

这个方法没有什么可说的,要注意的一个点就是cacheEnabled,开启了这个的话,那么创建的执行器会封装到缓存的执行器CachingExecutor中,这也就是装饰器模式的一部分,这个和二级缓存有关系的,二级缓存都在CachingExecutor中实现的,所以要开启二级缓存,那么cacheEnabled必须是为true才行,如果没有开启cacheEnabled,那么二级缓存是不起作用的;如果开启了cacheEnabled,没有开启二级缓存,那么在CachingExecutor中如果也会调用这里创建的执行器SimpleExecutor中的查询方法,当然是调用BaseExecutor,而SimpleExecutor的父类是BaseExecutor,所以整个装饰器模式就串起来了。

 

3.selectOne分析

3.1.二级缓存

selectOne方法就是查询单条数据的,在mybatis中,不管是查询单条数据还是查询列表数据,都会调用query方法,如果是单条的方法,那么要判断结果是否只有一条,如果是多条就要报错,所以这里只需要看selectlist方法即可

 

<select id="queryUser" resultMap="all" resultType="uu" >
 SELECT * FROM USER where  id=#{id}
</select>
/**
 * rowBounds这个参数是用来计算缓存的key使用的,但是可以自己生成
 * @param statement Unique identifier matching the statement to use.
 * @param parameter A parameter object to pass to the statement.
 * @param rowBounds  Bounds to limit object retrieval
 * @param <E>
 * @return
 */
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //这里获取声明对象,就是mapper中每个select中的标签对象
    //这里通过select中的ID或者命名空间+select中的ID获取一个声明的对象
    //然后通过sql执行器去查询
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

代码中的configuration.getMappedStatement(statement)就是拿到在mapper配置文件中的queryUser的声明信息,包括了sql语句,参数类型,返回类型等等,拿到这个声明就可以进行一系列的操作,具体操作看后面的;

在openSession的时候就知道了执行器executor创建的是一个CachingExecutor执行器,而且是封装了SimpleExecutor执行器的,所以这里的executor.query是调用的CachingExecutor中的query方法

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  /**
   * 获取boudSql对象,包括ognl的解析,包括if where、foreach等的解析都在
   * getBoundSql方法里面处理,这个方法有点复杂,反正就是判断sql是动态sql
   * 还是一般的sql,如果是${},那么就是动态sql,如果是#{}就是一般的sql,解析的
   * 类都不一样,动态sql是DynamicSqlSource,一般的sql是RawSqlSource
   * 包括if、where、foreach等都是用了不同的类去解析的,mybatis对于动态sql中
   * 每一个标签都使用了不同的类去解析,比如在一个查询中
   * SELECT * FROM USER
   *      <where>
   *          <if test="id != null">
   *              id = #{id}
   *          </if>
   *          <if test="name != null">
   *              and name = #{name}
   *          </if>
   *      </where>
   *      这个where都是单独使用了一个类进行解析的,而且会自动将where后面的
   *      and删除,就是where后面的第一个条件会删除and or 等操作符
   *
   *
   */
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //创建一级缓存key,一级缓存mybatis是默认开启,没有办法关闭
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    /**
     * 这里的Cache是二级缓存,二级缓存要在mybatis中配置了开启才会使用
     * 也就是说一级缓存默认开启,不能关闭,二级缓存需要手动开启
     * 而在使用的时候先获取二级缓存,如果二级缓存有数据就从二级缓存中获取
     * 二级缓存和一级缓存只能用一个,如果二级缓存开启了就使用二级缓存,不会使用一级缓存
     *一级缓存是没有办法关闭的,是默认开启的
     * 二级缓存和一级缓存是平级的,Executor有简单、复用、批量的模式,而还有一个带缓存的
     * 也就是CachingExecutor,如果二级缓存开启的话,那么不管是复用的、批量的、还是简单的
     * 都是封装到二级缓存的CachingExecutor中,通过装饰器模式来调用数据库的方法
     * 所以开启了二级缓存,那么就会调用CachingExecutor,如果找不到数据才会去调用创建的执行器delegate
     * 我的这个mybatis版本,cacheEng是默认开启的
     * 二级缓存开启需要配合useCache使用,而只要是select的查询都会默认开启
     * 也就是说针对查询,一级和二级缓存都是默认开启的,先获取二级缓存
     * cache配合二级缓存使用,也就是说只有在mapper中配置了cache,那么
     * 你开启的二级缓存才会有效果
     * 也就是说开启了cacheEnabled=true并且在mapper中配置了cache并且在select中
     * 配置了useCache才会生效
     * Mybatis.xml:<setting name="cacheEnabled" value="true"/>
     * Mapper.xml:<cache flushInterval="1000"></cache>
     * select:useCache="true"
     * 才会生效二级缓存
     * 如果在select中 配置了flushCache="true",那么会清空二级缓存
     */
    Cache cache = ms.getCache();
    if (cache != null) {
      //这里是清空缓存,在select等标签中设置了flushCache=true的情况下会清除缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
          //从缓存中获取数据,如果数据为空,还是需要去数据库中查询
        List<E> list = (List<E>) tcm.getObject(cache, key);
//        这里满足的是二级缓存,二级缓存有数据就从二级缓存中获取,如果二级缓存中没有,从数据查询出来,然后也会放入二级缓存
        if (list == null) {
          //从数据库中获取
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //查询出来的数据放入二级缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //如果没有开启二级缓存就通过装饰器类去查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

既然说到了二级缓存,那么就要理解下这个二级缓存的一个执行原理和是怎么添加二级缓存的,二级缓存是针对于mapper的缓存,只需要在开启了cacheEnabled的情况下,然后在mapper中添加<cache />标签,就可以开启二级缓存了,但是要明白这个cache标签是在哪里解析的,二级缓存cahce是针对mapper的缓存,所以cahce的解析肯定就是在解析mapper的时候了,所以我们看下解析mapper的源码,回到解析全局配置文件的地方,找到解析mapper的位置

java sql 子查询解析库 java实现sql查询分析_java_04

/**
 * 解析二级缓存
 * @param context
 */
private void cacheElement(XNode context) {
  if (context != null) {
    /**
     * 这里解析二级缓存的type,我们知道二级缓存的数据是放在硬盘上的,所以开启二级缓存存放的数据
     * 必须要是能够序列化的,这个mybatis提供了一个默认的type,但是mybatis也支持二级缓存的数据
     * 缓存到第三方缓存框架中,比如redis,但是需要你自己去实现二级缓存的缓存类,实现缓存到redis中
     */
    String type = context.getStringAttribute("type", "PERPETUAL");
    /**
     * 知道的二级缓存的type,看是佛叜别名注册表中存在,如果不存在,那么就会根据集配站的缓存
     * type通过反射得到一个calss,也就是我们自定义了二级缓存,我们可以把这个二级缓存的实现类
     * 通过别名注册到缓存中,然后在cahce中的type就可以直接定义别名也是可以使用的,
     * 如果不想通过这种方式,执行配置type为全限定名就可以了
     */
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    /**
     * 这个是缓存的淘汰机制,默认采用的是LRU,就是淘汰最少使用的
     * 和配置 type是一样的,可以配置到别名缓存中,也可以配置全限定名
     */
    String eviction = context.getStringAttribute("eviction", "LRU");
    //找到你配置的淘汰机制
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    //缓存刷新的时间间隔
    Long flushInterval = context.getLongAttribute("flushInterval");
    //缓存的大小
    Integer size = context.getIntAttribute("size");
    //缓存的可读状态
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    Properties props = context.getChildrenAsProperties();
    //通过建造者模式添加多个缓存的实现类,每个类实现不同的功能
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

在二级缓存的详细源码中,有一篇博客讲我觉得还是比较详细的,这里分享出这篇博客,有兴趣可以去看下;

myatis中的一级缓存是session级别的缓存,当session关闭或者提交过后,那么一级缓存就会失效了,而二级缓存是可以跨线程的缓存,也就是说不同的session,也是具有效果的,开启二级缓存:

全局配置文件中:

<setting name="cacheEnabled" value="true"/>

 

mapper配置文件中:

 

<cache></cache>
cache中的参数配置:
如果没有配置:mybatis会默认设置
type CDATA #IMPLIED 
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED

二级缓存和一级缓存不能同时生效的,所以在开启了二级缓存过后,不同的SqlSession都能够访问到二级缓存的内容,但是第一次二级缓存中肯定是没有数据的,所以会去查询数据库,然后放入二级缓存,但是查询的时候是通过执行器的装饰器类去查询的,也会放入一级缓存,所以如果说第一个sqlsession没有关闭或者提交的情况下,二级缓存是不会生效的,这和事务有关系,因为第一个sqlSession没有提交的话,那么为了防止读取的缓存数据不是脏收据,所以需要第一个sqlSession提交过后,那么二级缓存才会生效;

还有一个就是二级缓存的数据是缓存到硬盘上或者第三方的软件系统中的,所以要缓存的对象必须是序列化的,否则要报错的。

比如下面的例子:

InputStream input = Resources.getResourceAsStream("MyBatis.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
SqlSession sqlSession = sessionFactory.openSession();
Object one = sqlSession.selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);
System.out.println(one);
sqlSession.close();
SqlSession sqlSession2 = sessionFactory.openSession();
one = sqlSession2.selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);
System.out.println(one);

java sql 子查询解析库 java实现sql查询分析_java sql 子查询解析库_05

我查询两次,日志只打印了一次sql,证明只查询了一次数据库,而且第二次去查询的时候命中率是0.5,所以是启用了二级缓存的。结果已经知道了,那么我们就来分析下这个二级缓存和一级缓存,我们还是看下面的查询方法:

java sql 子查询解析库 java实现sql查询分析_java sql 子查询解析库_06

 

 

当开启了二级缓存过以后,cache是不为空的,比如就上面的测试代码逻辑中;第一个selectOne的时候,首先进入到if逻辑,并且第一次缓存中是没有数据的,所以这个时候要去调用

list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

这行代码的,而这行代码会进入到BaseExecutor的query中,这个是去查询数据库并且放入了一级缓存,但是查询返回过后这里又放入了二级缓存的对象中,那么是不是就很矛盾,其实不然,不要被这个逻辑绕晕了,首先要明白的是一级缓存和二级缓存不能同时存在的这句话其实有病句的,因为当第一次请求的时候缓存是没有的,那么查询会交给BaseExecutor去执行,执行成功过后 会放入一级缓存,但是这里有放入了二级缓存,其实这个时候只有一级缓存是生效的,二级缓存是没有生效的,不行你把上面的测试代码的close方法删除掉,那么永远都不会在二级缓存中找到数据,永远都是去了一级缓存中找到了数据,但是只能是同一个sqlsession的作用域内,因为一级缓存是session级别的缓存,不同的sqlSession有不同的一级缓存。那么为什么二级缓存不生效呢?原因就在于我上面说的当第一次查询的时候数据是放入到了一级缓存中,但是这个时候第一个sqlSession没有关闭或者提交,那么后面的请求怎么就知道了你放入缓存的这个sesion没有修改这缓存中的数据呢?万一你修改了,那么一级缓存和二级缓存都有这个数据,那不是就产生了脏数据了?所以这个和事务有关系,只有等你放入一级缓存的sqlSession关闭或者提交了,那么才会触发到二级缓存中去,感觉就是缓存移动一样,其实不一样的,我们看上面的代码中放入二级缓存的的代码是:tcm.putObject(cache, key, list);

这个tcm是TransactionalCacheManager,就是一个事务缓存管理器

主义这个tcm,我们看下放入的代码:

public void putObject(Cache cache, CacheKey key, Object value) {
  getTransactionalCache(cache).putObject(key, value);
}
private TransactionalCache getTransactionalCache(Cache cache) {
  return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
@Override
public void putObject(Object key, Object object) {
  entriesToAddOnCommit.put(key, object);
}

注意看,这个缓存事务管理器先得到是那个缓存,Cache的类型有很多

java sql 子查询解析库 java实现sql查询分析_spring_07

 

 

在解析全局配置文件中解析往Configuration中添加Cache的时候就添加了这里面的很多个,具体可以去debug下,添加的时候是通过建造者模式添加的,这里就不多说了。

得到过后,这个事物管理器就往entriesToAddOnCommit这个方法中添加数据。

如果说第一次查询的那个SqlSession没有提交或者关闭的情况下,这个时候我们开启第二个session去查询,那么会调用List<E> list = (List<E>) tcm.getObject(cache, key);这个方法,而这个方法就是去缓存中获取数据的,看下源码:

public Object getObject(Object key) {
  // issue #116
  Object object = delegate.getObject(key);
  if (object == null) {
    entriesMissedInCache.add(key);
  }
  // issue #146
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}

这里获取又是从delegate中去获取的,这个delegate是一个装饰器类,就是上面图中的Cache下面的一个装饰器类,也就是它的一个实现类,这里的这个装饰器类是SynchronizedCache,很明显获取不到缓存数据,因为mybatis的设计者就是考虑到可能会读取到脏数据,所以只有放入缓存的session没有关闭或者提交的情况下是不可能让其他线程开启了二级缓存能够获取到缓存数据的,所以我们看下面sqlSession关闭提交的代码就明白了,要理解事务的概念,事务是干嘛的,就是为了产生脏数据而产生的一种控制,所以要明白这个概念,那么mybatis的二级缓存和一级缓存就明白了。

 

@Override
public void close(boolean forceRollback) {
  try {
    // issues #499, #524 and #573
    if (forceRollback) {
      tcm.rollback();
    } else {
      tcm.commit();
    }
  } finally {
    delegate.close(forceRollback);
  }
}

这个就是clsoe方法,这里肯定是执行到了commit,因为正常执行嘛,我们看下commit方法

 

public void commit() {
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}

transactionalCaches这个值又是从哪里来的呢?transactionalCaches就是在第一次查询出结果放入二级缓存的时候添加进去的,transactionalCaches这里面其实就是SynchronizedCache,所以这里循环这个集合,在看txCache的commit

 

public void commit() {
  if (clearOnCommit) {
    delegate.clear();
  }
  flushPendingEntries();
  reset();
}
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}
private void reset() {
  clearOnCommit = false;
  entriesToAddOnCommit.clear();
  entriesMissedInCache.clear();
}

看到这里是不是就明白了,sqlSession1的关闭的会调用到了flushPendingEntries这个方法,这个方法将第一次放入二级缓存变量entriesToAddOnCommit中的数据拿出来重新放入了delegate的putObject中,是不是就明白了我上面说的缓存移动的一个概念,虽然不太准确,但是也是一种移动思想,先放入一个变量entriesToAddOnCommit,这个算是一个临时的变量吧,然后等第一个事务提交过后,将这个临时变量拿出来重新放入到delegate中,这个时候所有的线程只要是查询这个缓存说句都可以从delegate中得到缓存数据了,这个就是mybatis设计二级缓存的一个思想,整个思想中就是为了不能出现缓存脏数据而设计的一套机制,理解即可。

 

java sql 子查询解析库 java实现sql查询分析_java sql 子查询解析库_08

二级缓存的重要执行流程如上图,在网上有个博客有个图描述了整体的流程,我这里盗个图:

java sql 子查询解析库 java实现sql查询分析_java sql 子查询解析库_09

引用博客地址:

 

3.2.一级缓存

二级缓存分析完成过后,就开始分析一级缓存了,一级缓存就简单多了

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  //如果没有关闭session的情况下才可以执行查询
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    //如果在select中配置了flushCache=true,那么会清空一级缓存的数据
    clearLocalCache();
  }
  List<E> list;
  try {
    /**
     * 这里先从一级缓存中获取
     * 如果一级缓存中获取到数据,就直接返回这个数据,否则从数据库中获取数据
     * 一级缓存是默认存在,没有办法关闭的缓存
     */
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //缓存没有,就自己从数据库中去获取,获取到过后放入一级缓存中
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //执行数据库查询
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  //将查询的数据放入一级缓存
  localCache.putObject(key, list);
  //StatementType的默认是PREPARED
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

 

 

一级缓存非常简单,就是默认开启的,没有办法关闭的,这里 贴了源码和注释就不在做分析了,mybatis的一级缓存是session的缓存,所以是回话期间的缓存,如果要是session关闭或者提交的情况下,缓存就会关闭

InputStream input = Resources.getResourceAsStream("MyBatis.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
SqlSession sqlSession = sessionFactory.openSession();
Object one = sqlSession.selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);
System.out.println("第一次查询:"+one);
Object two = sqlSession.selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);

System.out.println("第二次查询:"+two);

对象one和two的查询条件是一模一样的,那么第二次查询肯定是去缓存中查询,这里要清楚的一个概念是一级缓存针对的是内存缓存,而二级缓存是需要持久化到硬盘的缓存

因为one和two都是同一个session范围内的,所以two肯定是从缓存中获取的,看下输出:

java sql 子查询解析库 java实现sql查询分析_mybatis_10

sql只执行了一次,所以第二次查询肯定从缓存中读取的,而前面说了一级缓存是回话的缓存,那么如果我重新创建一个session呢?

 

Object one = sqlSession.selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);
System.out.println("第一次查询:"+one);
Object two = sessionFactory.openSession().selectOne("com.mybatis.example.mapper.UserMapper.queryUser", 1);

System.out.println("第二次查询:"+two);

输出:

java sql 子查询解析库 java实现sql查询分析_mybatis_11

所以一级缓存是针对session的缓存,因为缓存对象是在执行器中,而执行器是每次sesison开启的时候创建的,每次新创建的,所以一级缓存不能跨线程使用。

 

4.数据库查询核心分析(queryFromDatabase)

上面分析了缓存数据是如何管理的,一级缓存和二级缓存的关系,那么如果是第一次查询数据,不管是一级缓存还是二级缓存,都不会使用,这个时候就要去数据库查询了,所以这里简单分析下mybatis是如何去数据库查询的,mybatis查询数据库简单来说就是解析出了sql,然后创建ParameterHandler、StateMentHandler、ResultSetHandler等处理器去处理的,简单来说就是这些处理器完成了数据库的查询并且将查询结果封装成对象返回,其中ParameterHandler是参数处理器,就是处理参数的,StateMentHandler是处理JDBC预处理的,ResultSetHandler是处理查询结果的处理器,我们知道mybatis是ORM框架,什么是ORM框架,就是对象关系映射框架,所以最后一点就是要将查询的结果封装成O(Object)否则就不是ORM框架了,所以ResultSetHandler就是将查询结果封装成目标对象的,但是在这里需要明白的一点就是什么呢,在之前的笔记中写了mybatis中的插件机制,都知道了mybatis的4大插件,前面将了执行器Executor,执行器就是可以嵌入的一个插件,而其他三个插件就是ParameterHandler、StateMentHandler、ResultSetHandler,所以这里到时候就不演示插件的使用了。

java sql 子查询解析库 java实现sql查询分析_spring_12

从图片可以看出BaseExecutor中的queryFormDatabase方法其实它什么鸡毛也没干,BaseExecutor是一个组件抽象器,因为Executor是一个装饰器,所以这里交给了装饰器类去实现的这个数据库查询,所以要明白装饰器的一个设计理念。这里使用的执行器是默认的执行器也就是SimpleExecutor

 

4.1.SimpleExecutor.doQuery

 

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      //通过声明对象得到配置对象
      Configuration configuration = ms.getConfiguration();
      //这里通过装饰器模式得到一个StatementHander对象,根据StatementType得到一个StatementHandler,statementType默认类型为PREPARED,这个type是解析mapper默认设置的
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//    进行sql的预处理,获取一个连接,通过连接对象创建一个预处理对象,然后通过预处理对象填充参数
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行查询,然后封装查询结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

StatementHandler 这个的创建又是装饰器模式,不晓得mybatis的设计人员为什么这么喜欢这个模式,mybatis的源码中这个设计模式占比80%有的吧,我们先看下面怎么创建的

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  //根据策略模式得到一个StatementHandler
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  //在创建每个StatementHandler时,执行interceptorChain中所有的拦截器,也就是在执行每个sql的时候都会执行下拦截器中的plugin方法
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}
public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
  ....

RoutingStatementHandler 是一个构件,其实它什么也不做,它把所有的事情都交给了具体的装饰器类delegate去执行的,我们看下类图:

java sql 子查询解析库 java实现sql查询分析_spring_13

 

所以了解这种模式就很清楚mybatis要干什么了。在创建PreparedStatementHandler的时候会去调用它的父类的构造BaseStatementHandler,在base中也进行了其他两个Handler的创建,以及插件的创建

 

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;

  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();

  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }

  this.boundSql = boundSql;

  /**
   * 这里创建了参数处理器和结果集处理器
   * 并且会执行参数处理的插件和结果集处理的插件
   */
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

到这里mybatis的4大插件都已经分析完成了,下面就开始分析sql的预处理

 

4.1.1.prepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  //获取一个连接对象Connection
  Connection connection = getConnection(statementLog);
  //获取一个预处理对象,通过Connection创建预处理对象(这个是调用jdbc本身的api)
  stmt = handler.prepare(connection, transaction.getTimeout());
  //参数填充(也是调用的jdbc的参数填充)
  handler.parameterize(stmt);
  return stmt;
}

 

上面的这个预处理没有啥可说的,跟下代码就 明白了,做的事情就是:

1.通过数据源获取一个连接;

2.通过连接进行sql预处理,得到预处理对象;

3.预处理对象进行参数填充;

4.最后返回这个预处理对象。

 

4.1.2.预处理查询数据库

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
 //执行查询
  ps.execute();
  //将查询出来的数据封装到对象中
  return resultSetHandler.handleResultSets(ps);
}

 

4.1.3.封装数据到对象

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  //定义查询对象返回的结果集
  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  /**
   * 包装第一条查询的数据对象,我们知道查询出来的对象都是ResultSet
   * 这里将查询出来的ResultSet.next对象封装到ResultSetWrapper中
   * 并且ResultSetWrapper中还包含了这一条数据锁包含的数据类型 ,实体属性,实体属性所对应的
   * class类型,比如
   * rs=ResultSet.next();
   * columnNames:实体属性列表如[id=null,name=null,age=null]
   * jdbcTypes:这一条数据的每一项数据的类型[INTEGER,VARCHAR,INTEGER]
   * classNames:实体属性所对应的class类型[java.lang.INTEGER,java.lang.String,java.lang.INTEGER]
   *所以下面要做的事情就是将rs中的数据根据columnNames、jdbcTypes、classNames得到一个实体对象,填充了
   * 实体对象的值,然后添加到multipleResults中,然后开始获取下一条 rsw = getNextResultSet(stmt);
   */
  ResultSetWrapper rsw = getFirstResultSet(stmt);

  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  while (rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = resultMaps.get(resultSetCount);
    //填充对象的属性值,然后添加到列表multipleResults中
    handleResultSet(rsw, resultMap, multipleResults, null);
    //获取下一条
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  /**
   *下面是对ResultSets的处理
   */

  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

  return collapseSingleResultList(multipleResults);
}

 

到这里mybatis的整体执行流程就完成了,这里只分析了查询接口,更新删除插入流程也都差不多了,但是需要注意的是查询的时候如果开启了二级缓存或者开启了一级缓存,那么至少有一个缓存是开启的,所以在更新删除插入数据的时候就要清除缓存了,那么更新删除插入这里是怎么做的呢,这里简单看下,先看二级缓存的,因为只要开启了CacheEnable,那么执行器的装饰器构建CachingExecutor都会经过这里

 

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
  Cache cache = ms.getCache();
  if (cache != null && ms.isFlushCacheRequired()) {
    tcm.clear(cache);
  }
}

在更新数据之前先要清空二级缓存的数据,如果开启了二级缓存并且flushCache设置为true的时候就会清空二级缓存,但是这个值默认是多少呢?如果细心的话,前面的解析全局配置里面就写了,flushCache这个值是只要非select的方法都是设置的true,所以说只要是更新、删除、插入都会调用这个clear方法来清空二级缓存的数据

java sql 子查询解析库 java实现sql查询分析_java sql 子查询解析库_14

而一级缓存呢?

java sql 子查询解析库 java实现sql查询分析_二级缓存_15

 

一级缓存也会清空本地缓存,在更新、删除、插入的时候。

 

5.mybatis涉及的设计模式

Builder模式:例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;

工厂模式:例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;

单例模式:例如ErrorContext和LogFactory;

代理模式:Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;

组合模式:例如SqlNode和各个子类ChooseSqlNode等;

模板方法模式:例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;

适配器模式:例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;

装饰者模式:例如Cache包中的cache.decorators子包中等各个装饰者的实现;

迭代器模式:例如迭代器模式PropertyTokenizer;

 

 

6.mybatis整合spring

mybatis如果是单独使用的话,会有些问题,比如说sqlSession,这个类就不是一个线程安全的类,而且如果单独使用的话一级缓存是没有办法关闭的,如果你不想在session的范围内缓存太多数据的话也是没有办法关闭的,但是现在基本上mybatis都没有单独使用的情况,有的也比较好了,现在基本上都是和spring进行整合的,至于myabtis如何交给spring管理的这个原理分析之前的spring专题就已经分析了,这里只说下为什么整合到spring中过后,那么就可以使一级缓存失效呢,这里稍后分析;还是大概看下是如何整合的吧,在官网上有很详细的说明,官网地址:http://mybatis.org/spring/zh/index.html ,在mybatis与spring整合的项目中有两个非常重要的类,一个是SqlSessionFactoryBean和SqlSessionTemplate,其实看了前面的分析都知道了,SqlSessionFactoryBean就是为了构建SqlSessionFacatory的,而SqlSessionTemplate就是为了构建SqlSession的,所以大概配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath*:mapping/*.xml" />
    </bean>


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true" />
        <property name="username" value="root" />
        <property name="password" value="111111" />
        <property name="initialSize" value="1" />
        <property name="minIdle" value="1" />
        <property name="maxActive" value="10" />
        <property name="maxWait" value="60000" />
        <property name="timeBetweenEvictionRunsMillis" value="50000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="20" />
        <property name="filters" value="stat" />
    </bean>


    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory" />

    </bean>


</beans>

 

 

SqlSessionFactoryBean:是一个工厂bean,在spring中是一个FactoryBean类型

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
        ....

SqlSessionFactoryBean不仅仅是一个FactoryBean还是一个初始化bean还是一个监听器,做的事儿有点多哦

 

既然是一个FactoryBean,那么在得到实例对象的其实是调用的getObject

 

@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}

返回的就是一个SqlSessionFactory,而afterPropertiesSet是在初始化后会来调用的,这里getObejct的时候如果为空,也会来调用

afterPropertiesSet:

 

@Override
public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
      "Property 'configuration' and 'configLocation' can not specified with together");

  this.sqlSessionFactory = buildSqlSessionFactory();
}
//和mybatis的读取全局配置文件一样的流程
//只是这里不是读取的配置文件,而是整合了spring过后,通过参数设置注入得到
//包括mapper配置文件的读取解析,都是一样的,只是换了一种方式而已
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;

  XMLConfigBuilder xmlConfigBuilder = null;
  if (this.configuration != null) {
    targetConfiguration = this.configuration;
    if (targetConfiguration.getVariables() == null) {
      targetConfiguration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      targetConfiguration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    targetConfiguration = xmlConfigBuilder.getConfiguration();
  } else {
    LOGGER.debug(
        () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    targetConfiguration = new Configuration();
    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
  }

  Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
  Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
  Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }

  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
  }

  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
  }

  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

  if (!isEmpty(this.typeHandlers)) {
    Stream.of(this.typeHandlers).forEach(typeHandler -> {
      targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
      LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
    });
  }

  targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

  if (!isEmpty(this.scriptingLanguageDrivers)) {
    Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
      targetConfiguration.getLanguageRegistry().register(languageDriver);
      LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
    });
  }
  Optional.ofNullable(this.defaultScriptingLanguageDriver)
      .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
    try {
      targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();
      LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));

  if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
      LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    } else {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
        LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
      }
    }
  } else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
  }

  return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

这个方法最核心的方法就是buildSqlSessionFactory,构建一个SqlSessionFactory,这个方法做的事情就是和mybatis启动读取全局配置文件的流程一样的,最终将全局配置文件读取完成过后构建出一个DefaultSqlSessionFactory返回。

 

6.1.SqlSessionTemplate

 

前面说了mybatis中的SqlSession不是一个线程安全的类,为什么呢?因为在一个线程中,如果只有一个SqlSession,那么它就不是一个线程安全的,在mybatis中,SqlSession只有一个实现也就是DefaultSqlSession

java sql 子查询解析库 java实现sql查询分析_java_16

官方都写了注释表示它不是一个线程安全的类,所以在多线程下是有线程安全问题的,那么整合了spring过后就成了一个线程安全的类了,那么spring是怎么做到的呢?所以这些答案也就在SqlSessionTemplte中,这里重点分析下怎么就变成了一个线程安全的类了。

在spring的配置文件中注入的时候是这样写的:

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory" />

    </bean>

这是通过构造方法进行注入的,并且注入的是一个sqlSessionFactory,所以在SqlSessionTemplate要找这个构造方法:

 

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

最终会调用到这个构造方法,看到上面的最后一行代码了吗,sqlSessionProxy是一个SqlSession,也就是操作CRUD的一个类,这里创建的是一个代理类,具体的代理是SqlSessionInterceptor,所以mybatis整合了spring的核心是一个线程安全的类应该就是这个代理类起的作用

SqlSessionInterceptor

/**
 * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
 * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
 * {@code PersistenceExceptionTranslator}.
 * 这个是创建sqlSession的时候的一个代理类,这个代理是JDK的动态代理Proxy
 * 看到这个代理类,肯定就直接看invoke方法了
 *
 */
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //先去得到一个SqlSession
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      /**
       * 为什么说整合了spring过后,mybatis的一级缓存就失效了,看到下面的代码sqlSession.commit(true)
       * 是不是就明白了,分析mybatis源码的时候过后,mybatis的一级缓存是session的缓存,只有等
       * session提交或者关闭的情况下,一级缓存才会失效,所以整合了spring过后,每次使用完成了自己提交了
       * 所以一级缓存就失效了。
       */
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
      ......
      }
      /**
 *
 * @see SpringManagedTransactionFactory
 * 这个方法的核心在于TransactionSynchronizationManager,这个是spring的事务管理器,还记得spring专题的时候说过
 * spring的事务管理器就是将所有的操作放入线程的缓存中来控制事务的,那么其实这里也是类似的
 * 比如说在多线程中如何保护线程安全,就是将每个线程创建的对象放入线程缓存中ThreadLocal,然后每个线程操作自己的变量对象
 * 这样就会达到线程安全的目的
 * 所以这里的执行逻辑是每次线从当前线程缓存中获取整个SqlSession,如果有,证明这个SqlSession之前当前线程的之前操作中已经放入了
 * 如果没有,则证明我这个线程是第一个来拿这个SqlSession,然后创建了,放进去
 * 所以核心就在于ThreadLocal来保证的Sqlsession的线程安全
 */
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

  //从事物管理器中拿到SqlSessionHolder,看当前线程是否已经创建了
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 //创建SqlSesssion
  SqlSession session = sessionHolder(executorType, holder);
  //如果创建了,就直接返回
  if (session != null) {
    return session;
  }

  //没有这里就开始创建一个新的SqlSession,创建流程就是调用了mybatis的openSession

  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);
  //然后将新创建的注册进去,以便当前线程下次还需要
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}