【Java EE】MyBatis的解析和运行原理

MyBatis的解析和运行原理

MyBatis的运行过程分为两大步:第1步,读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory;第2步,SqlSession的执行过程。

构建SqlSessionFactory过程

SqlSessionFactory是MyBatis的核心类之一,其最重要的功能是提供创建MyBatis的核心接口SqlSession,所以要先创建SqlSessionFactory,为此要提供配置文件和相关的参数。MyBatis采用了Builder模式去创建SqlSessionFactory,在实际中可以通过SqlSessionFactoryBuilder去构建,其构建分为两步:

  • 第一步:通过org.apache.ibatis.builder.xml.XMLConfigurationBuilder解析配置的XML文件,读出所配置的参数,并将读取的内存存入org.apache.ibatis.session.Configuration类对象中。而Configuration是单例模式。
  • 第二步:使用Configuration对象去创建SqlSessionFactory。MyBatis中的SqlSessionFactory是一个接口,而不是一个实现类,为此MyBatis提供了一个默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。

配置的typeHandler都会被注册到typeHandlerRegistry对象中去,typeHandlerRegistry对象实际就是Configuration单例的一个属性,所以可以通过Configuration单例拿到typeHandlerRegistry对象,进而拿到我们所注册的typeHandler。

构建Configuration

在SqlSessionFactory构建中,Configuration是最重要的,它的作用是:

  • 读入配置文件,包括基础配置的XML和映射器XML(或注解)
  • 初始化一些基础配置,比如MyBatis的别名等,一些重要的类对象(比如插件、映射器、Object工厂、typeHandlers对象等)
  • 提供单例,为后续创建SessionFactory服务,提供配置的参数
  • 执行一些重要对象的初始化方法
    Configuration是通过XMLConfiguration去构建的,首先它会读出所有XML配置信息,然后把它们解析并爆粗在Configuration单例中。它会做如下初始化:
  • properties全局参数
  • typeAlias别名
  • Plugins插件
  • objectFactory对象工厂
  • objectWrapperFactory对象包装工厂
  • reflectionFactory反射工厂
  • settings环境设置
  • environments数据库环境
  • databaseIdProvider数据库标识
  • typeHandlers类型转换器
  • Mappers映射器

它们都会以类似typeHandlers注册那样的方法被存放到Configuration单例中,以便未来将其取出。

构建映射器的内部组成

当XMLConfiguration解析XML时,会将每一个SQL和其配置的内容保存起来,那么它是怎么保存的呢?
一般而言,在MyBatis中一条SQL和它相关的配置信息是由3个部分组成的,它们分别是MapperStatement、SqlSource和BoundSql。

  • MapperStatement的作用是保存一个映射器节点的内容。它是一个类,包括我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、resultMap、languageDriver等重要配置内存。它还有一个重要的属性SqlSource。MyBatis通过读取它来获得某条SQL配置的所有信息。
  • SqlSource是提供BoundSql对象的地方,它是MapperedStatement的一个属性。注意,它是一个接口,而不是一个实现类。对它而言有这么重要的几个实现类:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StateSqlSource。它的作用是根据上下文和参数解析生成需要的SQL。
  • BouncSql是一个结果对象,也就是SqlSource通过对SQL和参数的联合解析得到的SQL和参数,它是建立SQL和参数的地方,它有3个常用的属性:sql、parameterObject、parameterMappings。

parameterObject为参数本身,可以传递简单对象、POJO或者Map、@Param注解的参数。
parameterMappings是一个List,它的每一个元素都是PatameterMapping对象。
sql属性就是书写在映射器里面的一条被SqlSource解析后的SQL。

构建SqlSessionFactory

MyBatis会根据文件流先生成Configuration对象,进而构建SqlSessionFactory对象。真正的难点在于构建Configuration对象。

SqlSession运行过程

映射器(Mapper)的动态代理

先看这样的一句代码:

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);

MyBatis运用了Configuration对象的getMapper方法来获取对应的接口对象,而getMapper又运用了映射器的注册Mapperregistry来获取对应的接口对象,在这个过程中首先它会判断是否注册一个Mapper,如果没有则会抛出异常信息,如果有,就会启用MapperProxyFactory工厂来生成一个代理实例。
Mapper映射是通过动态代理来实现的。动态代理对接口的绑定,作用就是生成动态代理对象(占位),而代理的方法则被放到了MapperProxy类中。
在MapperProxy的源码中可以看到invoke方法逻辑,如果Mapper是一个JDK动态代理对象,那么会运行到invoke方法里边。invoke首先判断是否是一个类。然后会生成MapperMethod对象,它是通过cachedMappedMethod方法对其初始化的。最后执行execute方法,把SqlSession和当前运行的参数传递进去, 通过SqlSession对象去运行对象的SQL。
MyBatis为什么只用Mapper接口便能够运行,因为Mapper的XML文件和命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来。通过动态代理技术,让这个接口运行起来,而后采用命令模式。最后使用SqlSession接口的方法使得它能够执行对应的SQL。只是有了这层封装,就可以采用接口编程。

SqlSession下的四大对象

映射器就是一个动态代理对进入到了MapperMethod的execute方法,然后它经过简单地判断就进入了SqlSession的delete、update、insert、select等方法,那么这些方法是如何执行呢?
实际上SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,它们简称为四大对象

  • Executor:代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL,其中StatementHandler是最重要的。
  • StatementHandler:作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。
  • ParameterHandler是用来处理SQL参数的。
  • ResultSetHandler是进行数据集(ResultSet)的封装返回处理的,不常用。

Executor——执行器

MyBatis中有3中执行器。可以在MyBatis的配置文件中进行选择:

  • SIMPLE: 简易执行器,它没有什么特别的,默认执行器
  • REUSE:是一种能够执行重用预处理语句的执行器
  • BATCH:执行器重用语句和批量更新,批量专用的执行器
    MyBatis将根据配置类型去确定需要创建哪一种Executor,它的缓存则用CachingExecutor进行包装Executor。在运用插件时,拦截Executor就有可能获取这样的一个对象,在创建对象后,回去执行这样一行代码:
interceptorChain.pluginAll(executor)

这就是MyBatis的插件。它将构建一层层的动态代理对象,可以修改在调度真是的Executor方法之前执行配置插件的代码,这就是插件的原理。
以SIMPLE执行器SimpleExecutor的query为例:MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译和参数进行初始化。实现过程:它调用了StatementHandler的prepare()进行了预编译和基础的设置,然后通过StatementHandler的parameterize()来设置参数,最后使用StatementHandler的query方法,把ResultSetHandler传递进去,使用它组织结果返回给调用者来完成一次查询。这样焦点又转移到了StatementHandler对象上。

StatementHandler——数据库会话器

数据库会话器就是专门处理数据库会话的。MyBatis生成StatementHandler代码中,创建的真实对象是一个RoutingStatementHandler的对象,它实现了接口StatementHandler。和Executor一样,用代理对象做一层层封装。
RoutingStatementHandler不是真实的服务对象,它是通过适配模式来找到对应的StatementHandler来执行的。在MyBatis中,与Executor一样,RoutingStatementHandler分为3种:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。它所对应的是JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。
在初始化RoutingStatementHandler对象时,它会根据上下文环境决定创建哪个具体的StatementHandler对象实例。它定义了一个对象的适配器——delegate,它时一个StatementHandler接口对象,然后构造方法根据配置来适配对应的StatementHandler对象。它的作用时给3个接口对象的使用提供一个统一且简易的配适器。

一条查询SQL的执行过程:Executor先调用StatementHandler的prepare()方法预编译SQL,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterHandler设置参数,完成预编译,执行查询,update()也是这样的。如果是查询,MyBatis会使用ResultSetHandler封装结果返回给调用者。

ParameterHandler——参数处理器

MyBatis通过ParameterHandler对预编译语句进行参数设置,作用是完成对预编译参数的设置,其接口定义如下:

public interface ParameterHandler{
	Object getParameterObject();
	void setParameters(PreparedStatement ps) throws SQLException;
}

其中:

  • getParameterObject()方法的作用是返回参数对象
  • setParameters()方法的作用是设置预编译SQL语句的参数。

ResultSetHandler——结果处理器

ResultSetHandler是组装结果集返回的,接口定义如下:

public interface ResultSetHandler{
	<E> List<h> handleResultSets(Statement stmt) throws SQLException;
	void handleOutputParameters(CallableStatement cs) throws SQLException;
}

其中,handleOutputParameters()方法是处理存储过程输出参数的。重点是handleResultSets()方法,它是包装结果集的。MyBatis提供了一个DefaultResultHandler的实现类,在默认情况下都是通过这个类进行处理的。涉及使用JAVASSIST(或者CGLIB)作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回。

SqlSession运行总结

SqlSession的运行原理十分重要,是插件的基础,这里就一次查询或更新进行总结,SqlSession内部运行如图所示:
SqlSession内部运行图
SqlSession是通过执行器Executor调度StatementHandler来运行的。而StatementHandler经过了3步:

  • prepared预编译SQL
  • parameterize设置参数
  • query/update执行SQL
    其中,parameterize是调用parameterHandler的方法设置的,而参数是根据类型处理器typeHandler处理的。query/update方法通过ResultSetHandler进行处理结果的封装,如果是update语句,就返回整数,否则就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。这便是SqlSession执行的过程。
发布了279 篇原创文章 · 获赞 169 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/ARPOSPF/article/details/104435129