其他分享
首页 > 其他分享> > mybatis一文全解

mybatis一文全解

作者:互联网

mybatis核心组件

Configuration

Configuration是mybatis的全局配置类,保存了环境对象Enviroment(Environment表示数据源相关环境),各种配置信息,以及作为各种资源解析后的注册表。
例如,MapperRegister表示Mapper的注册表,TypeHandlerRegistryTypeHandler的注册表,TypeAliasRegistryTypeAlias的注册表,另外还以Map的形式保存了MappedStatement, ResultMap,ParameterMaps等的映射关系,其中key均是namespace + id的形式。

SqlSessionFactory

SqlSessionFactory是负责创建SqlSession的工厂。

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

主要是通过openSession()创建SqlSession。此外,还有一个返回全局配置对象的方法getConfiguration()。可以猜测其实现类应该会直接或间接的保持对Congfiguration的引用。
观察openSession()的参数,猜测创建SqlSession的方式有两种,一种直接基于传入的数据库连接Connection。另一种通过全局配置对象Configuration在获取数据源环境Enviroment,在获取环境。
SqlSessionFactory的默认实现是DefualtSqlSessionFactory

SqlSession

SqlSession表示某次数据库操作会话,因此SqlSession接口定义的主要是CRUD和事务操作的相关接口。
另外还有个重要的方法getMapper,可以返回对应Mapper的对象。

注意:SqlSession CRUD相关方法的参数第一个参数均为statement的字符串,这个字符串并非SQL语句,而是MappedStatement的ID。

SqlSession的默认实现是DefualtSqlSession
DefaultSqlSession不是线程安全的,使用者需要自己确保线程安全问题,或者是使用SqlSessionManager,它提供了SqlSession的线程安全管理。

Executor

执行器,负责真正执行数据库操作,并且提供了缓存的能力。
每个DefualtSqlSession内部都有一个Executor,在创建DefaultSqlSession实例时,同时创建了Executor对象,因此ExcecutorSqlSession是一对一绑定的。
Executor可以分为两类:
第一类是BaseExecutor以及子类,这类的Executor有操作数据库的能力,并且提供了mybatis的一级缓存。
第二类是CachingExecutor,它对第一个的执行器进行了包装,提供了二级缓存,并在二级缓存未命中时,委托给内部的第一类执行器处理。

MappedStatement

MappedStatement表示的是mapper.xml中定义的一个SQL节点。当创建Configuration对象在创建xml时,就会将一个个节点解析成对应的MappedStatement对象。
MappedStatement中大部分属性都可以在xml的定义中找到相关的配置。

四种处理器 TypeHandler,ParameterHandler,StatementHandler,ResultSetHandler

通常我们可以拓展这个接口实现自定义枚举类型与JDBC的转换类型。

SqlSource和BoundSql

一个SqlSource表示MappedStatement定义的Sql片段,一个SqlSource可能由多个SqlNode组成。
BoundSqlSqlSource应用了上下文环境(指用户输入参数)后得到的对象,对SqlSource中条件和参数做了筛选,形成的实际SQL(仍可能有'?'占位符)。

执行过程

以一个简单的程序作为入口来看看mybatis一次查询执行的主要流程。

class MybatisTest{
    public static void main(){
        //STEP 1
        String resource = "org/mybatis/example/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        //STEP 2
        try (SqlSession session = sqlSessionFactory.openSession()) {
        //STEP 3
        BlogMapper mapper = session.getMapper(BlogMapper.class);  
        //STEP 4
        Blog blog = mapper.selectBlog(101);
        }
    }
}

形成全局配置对象,构建SqlSessionFactory

第一步是读取全局的配置文件,解析文件形成我们的全局配置Configuration。 解析的过程主要是针对xml文件各节点的解析,本文目的为把握主体流程,这里不深入分析。
得到配置对象后,SqlSessionFacotryBuilder会将Configuration对象传入创建SqlSessionFactory对象。

打开SqlSession

得到SqlSessionFactory工厂对象后,可以通过openSession()方式获得SqlSession对象(默认是DefaultSqlSession)。

DefaultSqlSession的构造函数依赖三个参数,分别是ConfigurationExecutorautocommitConfiguration是全局的,而Executor却是跟DefaultSqlSession一一绑定的(也就是说在创建DefaultSqlSession的时候, 会创建一个新的Executor,并且这个Executor不会暴露给其他SqlSession使用),理解这一点对搞清一级缓存很有用。

获得代理对象

当调用SqlSession.getMapper()时,首先会从Congifuration的注册表中查找对应类型是否已经注册,没有则抛出异常。
如果存在,则通过MapperProxyFactory创建代理对象。
MapperProxyFactory主要是通过JDK动态代理创建代理对象的,这一过程分为两步:

注意:就算是相同的SqlSession,每次getMapper得到的代理对象也并非同一个,只不过对于相同SqlSession创建的Mapper而言,MapperProxy引用的SqlSession相同。

代理对象通过反射调用执行方法

既然代理对象是JDK动态代理创建的,那么其方法的执行最终会落到InvocationHandler,也就这里的MapperProxy的invoke中。
MapperProxy.invoke()又调用了MapperMethod.execute()
MapperMethod.execute()在SQL的执行前后做了两件事,处理参数,以及对执行结果进行计数,而核心的SQL执行还是交回给了SqlSession对象。

Executor执行器执行

SqlSession在执行CRUD时,会从Configuration查找对应的MappedStatement对象,然后将MappedStatement传递给Executor对象执行。
此时,如果开启了二级缓存,CachingExecutor会先从MappedStatement的Cache中查找,如果缓存未命中,CachingExecutor则会将查找任务委托给内部的BaseExecutor。而BaseExecutor则会先从内部的LocalCache中查找,如果缓存未命中,则将SQL的执行交给StatementHandler

StatementHandler执行SQL

StatementHandler的执行过程分为两个阶段:

当得到Sql的执行结果后,还会应用ResultSetHandler,将结果集转换成Java容器类。

用一副粗糙的图概括上述业务流程图:
image

一级缓存与二级缓存

什么是一级缓存

一级缓存是Executor内部的缓存机制。主要原理是BaseExecutor有一个叫localCache的字段用来存放这个会话的执行结果。因此,一级缓存是SqlSession内部的缓存(因为Executor和SqlSession是一一绑定的)。
一级缓存的有效期是某一次会话过程,一旦会话关闭,一级缓存也就失效。另外,如果会话中发生了增删改的写操作,一级缓存的会话同样会失效。

什么是二级缓存

二级缓存是MappedStatement的缓存,MappedStatement有一个Cache字段用来存放二级缓存。因此,我们常说二级缓存是跨SqlSession的。二级缓存默认是关闭的,如果希望开启二级缓存需要同时确保mybatis设置中的Cache打开,以及对应的MappedStatement开启了缓存。
那么二级缓存的实现原理是怎么样的?
我们知道二级缓存的使用者是CachingExecutor,在CachingExecutor执行查询前会先查看MappedStatement中是否存放对应的缓存。
如果缓存未命中,CachingExecutor会由内部的BaseExecutor执行数据库查询操作,得到查询结果后,CachingExecutor交给内部的TransactionCacheManager保存。只有当事务提交完成后,TransactionCacheManager保存的缓存才会写入MappedStatement的Cache中。
读者可以自己思考下这么做的用意。

二级缓存的脏读

因为二级缓存是与MappedStatement绑定的,换句话说就是和命名空间绑定的,假设存在这个一个情况,MappedStatement A 缓存了User的数据,但是MappedStatment B 可能也对User表进行了修改,但是 A中的缓存无法感知这一变化,缓存一直生效。这就产生了二级缓存的脏读问题。
为了避免上述问题,首先我们在开发的时候需要确保相应的规范,让相同表的操作尽量在相同的命名空间下。如果实在需要在不同的命名空间下操作相同的表,就需要CacheRef设置让二者使用相同的缓存。

自定义拦截器

mybatis通过Interceptor接口向用户提供了拓展的机制。
其底层实现原理依旧是利用了JDK的动态代理。当我们通过Configuraion.newExecutor()时会将创建得到Executor在经过动态代理包装一层,以达到实现拦截方法执行的目的。
此处InvocationHandler的实现是Proxy对象,可以看其invoke()方法的实现。

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //是否匹配拦截器的Signature
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        //将连接点的信息(方法,参数,目标对象)封装成 Invocation对象,传入由Interceptor执行
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

mybatis与Spring的整合

在mybatis与Spring集成的过程中,以下几个组件承担了重要角色:

标签:缓存,一文,MappedStatement,对象,二级缓存,SqlSession,mybatis,全解,throws
来源: https://www.cnblogs.com/insaneXs/p/12997368.html