转载:https://www.cnblogs.com/wuzhenzhao/p/11103017.html

先来看一下MyBatis 的编程式使用的方法:

public void testMapper() throws IOException {
  String resource = "mybatis-config.xml";
  InputStream inputStream = Resources.getResourceAsStream(resource);
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  SqlSession session = sqlSessionFactory.openSession();
  try {
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlogById(1);
    System.out.println(blog);
  } finally {
    session.close();
  }
}

  我们再来捋MyBatis 的主要工作流程:首先在MyBatis 启动的时候我们要去解析配置文件,包括全局配置文件和映射器配置文件,这里面包含了我们怎么控制MyBatis 的行为,和我们要对数据库下达的指令,也就是我们的SQL 信息。我们会把它们解析成一个Configuration 对象。接下来就是我们操作数据库的接口,它在应用程序和数据库中间,代表我们跟数据库之间的一次连接:这个就是SqlSession 对象。我们要获得一个会话, 必须有一个会话工厂SqlSessionFactory 。SqlSessionFactory 里面又必须包含我们的所有的配置信息,所以我们会通过一个Builder 来创建工厂类。

  我们知道,MyBatis 是对JDBC 的封装,也就是意味着底层一定会出现JDBC 的一些核心对象,比如执行SQL 的Statement,结果集ResultSet。在Mybatis 里面,SqlSession 只是提供给应用的一个接口,还不是SQL 的真正的执行对象。我们通过查看SqlSession源码发现,SqlSession 持有了一个Executor 对象,用来封装对数据库的操作。在执行器Executor 执行query 或者update 操作的时候我们创建一系列的对象,来处理参数、执行SQL、处理结果集,这里我们把它简化成一个对象:StatementHandler,这个就是MyBatis 主要的工作流程,如图:

MyBatis 架构分层与模块划分:

  在MyBatis 的主要工作流程里面,不同的功能是由很多不同的类协作完成的,它们分布在MyBatis jar 包的不同的package 里面。我们来看一下MyBatis 的jar 包(基于3.5.1)

跟Spring 一样,MyBatis 按照功能职责的不同,所有的package 可以分成不同的工作层次。我们可以把MyBatis 的工作流程类比成餐厅的服务流程。

  第一个是跟客户打交道的服务员,它是用来接收程序的工作指令的,我们把它叫做接口层。

  第二个是后台的厨师,他们根据客户的点菜单,把原材料加工成成品,然后传到窗口。这一层是真正去操作数据的,我们把它叫做核心层。

  最后就是餐厅也需要有人做后勤(比如清洁、采购、财务),来支持厨师的工作和整个餐厅的运营。我们把它叫做基础层。

来看一下这张图,我们根据刚才的分层,和大体的执行流程,做了这么一个总结。当然,从不同的角度来描述,架构图的划分有所区别,这张图画起来也有很多形式。我们先从总体上建立一个印象。

接口层:

  首先接口层是我们打交道最多的。核心对象是SqlSession,它是上层应用和MyBatis打交道的桥梁,SqlSession 上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。

核心处理层:

  接下来是核心处理层。既然叫核心处理层,也就是跟数据库操作相关的动作都是在这一层完成的。核心处理层主要做了这几件事:

  1. 把接口中传入的参数解析并且映射成JDBC 类型;
  2. 解析xml 文件中的SQL 语句,包括插入参数,和动态SQL 的生成;
  3. 执行SQL 语句;
  4. 处理结果集,并映射成Java 对象。

  插件也属于核心层,这是由它的工作方式和拦截的对象决定的。

基础支持层:

  最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存(请点击跳转至缓存详解)、日志、xml 解析、反射、IO、事务等等这些功能。

  这个就是MyBatis 的主要工作流程和架构分层。

从以上的编程式的例子来看,mybatis的工作流程大致可以分为一下四步:

  1. 通过建造者模式创建一个工厂类,定位,加载,解析配置文件的就是在这一步完成的,包括mybatis-config.xml 和Mapper 适配器文件。
  2. 通过SqlSessionFactory 创建一个SqlSession。
  3. 获得Mapper 对象。
  4. 调用接口方法(insert,delete,update,select)。

第一步配置解析过程:

  定位资源位置,加载资源,将xml加载成流这里就不分析了。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

  首先我们要清楚的是配置解析的过程全部只解析了两种文件。一个是mybatis-config.xml 全局配置文件。另外就是可能有很多个的Mapper.xml 文件,也包括在Mapper 接口类上面定义的注解。我们从mybatis-config.xml 开始。在之前已经分析了核心配置了,大概明白了MyBatis 有哪些配置项,和这些配置项的大致含义。这里我们再具体看一下这里面的标签都是怎么解析的,解析的时候做了什么。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  首先我们new 了一个SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个build 方法的重载,最终返回的是一个SqlSessionFactory 对象(单例模式)。我们点进去build 方法。这里面创建了一个XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 创建全局文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 调用解析,解析完构建默认的SessionFactory
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}

  XMLConfigBuilder 是抽象类BaseBuilder 的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:XMLMapperBuilder:解析Mapper 映射器,XMLStatementBuilder:解析增删改查标签。

  根据我们解析的文件流,这里后面两个参数都是空的,创建了一个parser。这里有两步,第一步是调用parser 的parse()方法,它会返回一个Configuration类。也就是配置文件里面所有的信息都会放在Configuration 里面。Configuration 类里面有很多的属性,有很多是跟config 里面的标签直接对应的。

public Configuration parse() {
   // 判断是否已经解析过
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
      // 解析的开始
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}

  首先会检查是不是已经解析过,也就是说在应用的生命周期里面,config 配置文件只需要解析一次,生成的Configuration 对象也会存在应用的整个生命周期中。接下来就是parseConfiguration 方法:

  这下面有十几个方法,对应着config 文件里面的所有一级标签。MyBatis 全局配置文件的顺序不可以颠倒。

private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
//解析settings标签
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}

  在settings标签的解析中,我们就可以看到对应官网上介绍的各种配置,及其默认值,所有的值,都会赋值到Configuration 的属性里面去。简单的来看一下:

private void settingsElement(Properties props) {
this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));//二级缓存
this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));//默认执行器类型
this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));//本地缓存级别
this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
this.configuration.setLogPrefix(props.getProperty("logPrefix"));
this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
}

  最后就是<mappers>标签的解析,对应匹配如下四种方式:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

  首先会判断是不是接口,只有接口才解析;然后判断是不是已经注册了,单个Mapper重复注册会抛出异常。如果是以资源位置及URL的配置方式,还需要创建Mapper解析器解析。

private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
//配置包的方式
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
               //解析完加入全局配置类,填充属性
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
//配置以资源的方式
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
//配置以URL的方式
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
// 配置以class单个接口 方式
} else {//都没配置报异常
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}

  XMLMapperBuilder.parse()方法,是对Mapper 映射器的解析。里面有两个方法:configurationElement()—— 解析所有的子标签, 其中 buildStatementFromContext()最终获得MappedStatement 对象。bindMapperForNamespace()——把namespace(接口类型)和工厂类绑定起来。

  无论是按package 扫描,还是按接口扫描,最后都会调用到MapperRegistry 的addMapper()方法。最后,MapperRegistry 也会放到Configuration 里面去。MapperRegistry 里面维护的其实是一个Map 容器,存储接口和代理工厂的映射关系。

public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//判断是否接口
if (this.hasMapper(type)) { //是否已经注册
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {//利用Map来维护mapper
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();//解析
loadCompleted = true;
} finally {
if (!loadCompleted) {//注册过程出现异常,移除
this.knownMappers.remove(type);
}
}
}
}

  除了映射器文件,在这里也会去解析Mapper 接口方法上的注解。在addMapper()方法里面创建了一个MapperAnnotationBuilder,我们点进去看一下parse()方法。parseCache() 和parseCacheRef() 方法其实是对@CacheNamespace 和@CacheNamespaceRef 这两个注解的处理。parseStatement()方法里面的各种getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap 等等。最后同样会解析成MappedStatement 对象,也就是说在XML 中配置,和使用注解配置,最后起到一样的效果。

public void parse() {
String resource = this.type.toString();
if (!this.configuration.isResourceLoaded(resource)) {
this.loadXmlResource();//加载XML资源
this.configuration.addLoadedResource(resource);
this.assistant.setCurrentNamespace(this.type.getName());
this.parseCache();//解析@CacheNamespace注解
this.parseCacheRef();//解析@CacheNamespaceRef注解
Method[] methods = this.type.getMethods();
Method[] var3 = methods;
int var4 = methods.length; for(int var5 = 0; var5 < var4; ++var5) {
Method method = var3[var5];
try {
if (!method.isBridge()) {
this.parseStatement(method);//处理各种注解
}
} catch (IncompleteElementException var8) {
this.configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
this.parsePendingMethods();
}

  最后会将这些解析的结果填充倒Configuration中。在这一步,我们主要完成了config 配置文件、Mapper 文件、Mapper 接口上的注解的解析。我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration 的实例。如下是第一阶段的时序图:

第二步会话创建过程:

  这是第二步, 我们跟数据库的每一次连接, 都需要创建一个会话, 我们用openSession()方法来创建。

SqlSession session = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;

DefaultSqlSession var8;
try {
//从先前所解析的配置中获取环境
Environment environment = this.configuration.getEnvironment();
//获取事务工厂类
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器类 默认SIMPLE
Executor executor = this.configuration.newExecutor(tx, execType);
//创建默认的SqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}

  这个会话里面,需要包含一个Executor 用来执行SQL。Executor 又要指定事务类型和执行器的类型。所以我们会先从Configuration 里面拿到Enviroment,Enviroment 里面就有事务工厂。创建事务会有以下两种选择:

  如果配置的是JDBC,则会使用Connection 对象的commit()、rollback()、close()管理事务。如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic。如果是Spring + MyBatis , 则没有必要配置, 因为我们会直接在applicationContext.xml 里面配置数据源和事务管理器,覆盖MyBatis 的配置。

  我们知道,Executor 的基本类型有三种:SIMPLE、BATCH、REUSE,默认是SIMPLE(settingsElement()读取默认值),他们都继承了抽象类BaseExecutor。

  • SimpleExecutor:每执行一次update 或select,就开启一个Statement 对象,用完立刻关闭Statement 对象。
  • ReuseExecutor:执行update 或select,以sql 作为key 查找Statement 对象,存在就使用,不存在就创建,用完后,不关闭Statement 对象,而是放置于Map 内,供下一次使用。简言之,就是重复使用Statement 对象。
  • BatchExecutor:执行update(没有select,JDBC 批处理不支持select),将所有sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement 对象,每个Statement 对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC 批处理相同。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//判断使用的执行器类型,之所以判断两次是防止手贱的人把执行器配置为空
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object 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);
}
if (this.cacheEnabled) {//是否启用二级缓存,启用了进行包装
executor = new CachingExecutor((Executor)executor);
}
//插件包装,转换成拦截器链interceptorChain,对于插件我下一篇博客会详细介绍
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}

  如果配置了cacheEnabled=ture,会用装饰器模式对executor 进行包装:newCachingExecutor(executor)。最终返回DefaultSqlSession,属性包括Configuration、Executor 对象。

  总结:创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,它是SQL 的执行者。

第三步获得Mapper 对象:

  现在我们已经有一个DefaultSqlSession 了,必须找到Mapper.xml 里面定义的Statement ID,才能执行对应的SQL 语句。找到Statement ID 有两种方式:一种是直接调用session 的方法,在参数里面传入Statement ID,这种方式属于硬编码,我们没办法知道有多少处调用,修改起来也很麻烦。另一个问题是如果参数传入错误,在编译阶段也是不会报错的,不利于预先发现问题。

Blog blog = (Blog) session.selectOne("com.wuzz.mapper.BlogMapper.selectBlogById", 1);

  所以在MyBatis 后期的版本提供了第二种方式,就是定义一个接口,然后再调用Mapper 接口的方法。由于我们的接口名称跟Mapper.xml 的namespace 是对应的,接口的方法跟statement ID 也都是对应的,所以根据方法就能找到对应的要执行的SQL。

BlogMapper mapper = session.getMapper(BlogMapper.class);

  在这里我们主要研究一下Mapper 对象是怎么获得的,它的本质是什么。DefaultSqlSession 的getMapper()方法,调用了Configuration 的getMapper()方法。继而调到 MapperRegistry.getMapper:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}

  我们知道,在解析mapper 标签和Mapper.xml 的时候已经把接口类型和类型对应的MapperProxyFactory 放到了一个Map 中。获取Mapper 代理对象,实际上是从Map 中获取对应的工厂类后,最终通过代理模式返回代理对象。

  那么JDK 动态代理和MyBatis 用到的JDK 动态代理有什么区别呢?JDK 动态代理代理,在实现了InvocationHandler 的代理类里面,需要传入一个被代理对象的实现类。而在mybatis里面,我们并没有实现了InvocationHandler 的代理类。不需要实现类的原因:我们只需要根据接口类型+方法的名称,就可以找到Statement ID 了,而唯一要做的一件事情也是这件,所以不需要实现类。在MapperProxy里面直接执行逻辑(也就是执行SQL)就可以。

  获得Mapper 对象的过程,实质上是获取了一个MapperProxy 的代理对象。MapperProxy 中有sqlSession、mapperInterface、methodCache。

第四步执行sql:

  由于所有的Mapper 都是MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {//首先判断是否需要去执行SQL,还是直接执行方法。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//Object 本身的方法和Java 8 中接口的默认方法不需要去执行SQL。
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
     //获取缓存 这里加入缓存是为了提升MapperMethod 的获取速度
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}

  接下来又调用了mapperMethod 的execute 方法,我们debug来看一下:

  MapperMethod 里面主要有两个属性, 一个是SqlCommand (SQL命令), 一个是MethodSignature(方法签名),这两个都是MapperMethod 的内部类。另外定义了多个execute()方法。

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;//根据不同的type 和返回类型 调用sqlSession 的insert()、update()、delete()、selectOne ()方法
switch(this.command.getType()) {
case INSERT:
       // 调用convertArgsToSqlCommandParam()将参数转换为SQL 的参数。
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT://查询的方法又要根据返回类型进行区分执行
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH://刷新statement
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
} if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}

  我们以查询  (mapper1.selectBlogById(1002))  为例 会走如下代码,会走到selectOne()方法。

public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

  这里抛得异常是不是很熟悉,经常有小伙伴在开发过程中会发现sql执行报这个异常,是因为定义的sql是查询的一个结果,可是返回了多个,这里会执行selectList(statement, parameter):

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {//获取初始化阶段所封装的对象先根据command name(Statement ID)从Configuration中拿到MappedStatement
       MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}

  这个ms 上面有我们在xml 中配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等。

  然后执行了Executor 的query()方法。前面我们说到了Executor 有三种基本类型,SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。那么在这里到底会选择哪一种执行器呢?我们要回过头去看看DefaultSqlSession 在初始化的时候是怎么赋值的,这个就是我们的会话创建过程。如果启用了二级缓存,就会先调用CachingExecutor 的query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。在没有开启二级缓存的情况下,先会走到BaseExecutor 的query()方法(否则会先走到CachingExecutor)。

  BaseExecutor:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

  从Configuration 中获取MappedStatement, 然后从BoundSql 中获取SQL 信息,创建CacheKey。这个CacheKey 就是缓存的Key。然后再调用另一个query()方法。

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());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {//queryStack 用于记录查询栈,防止递归查询重复处理缓存。flushCache=true 的时候,会先清理本地缓存(一级缓存)
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
//清除缓存
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {//从缓存中获取数据
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {//如果没有缓存,会从数据库查询:queryFromDatabase()
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
//如果LocalCacheScope == STATEMENT,会清理本地缓存。
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}

  从数据库查询(queryFromDatabase):

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     // 先在缓存用占位符占位。执行查询后,移除占位符,放入数据。
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {//执行Executor 的doQuery();默认是SimpleExecutor。
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}

  SimpleExecutor.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null; List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
} return var9;
}

  在configuration.newStatementHandler()中,new 一个StatementHandler,先得到RoutingStatementHandler。RoutingStatementHandler 里面没有任何的实现, 是用来创建基本的StatementHandler 的。这里会根据MappedStatement 里面的statementType 决定StatementHandler 的类型。默认是PREPARED ( STATEMENT 、PREPARED 、CALLABLE)。

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}

  StatementHandler 里面包含了处理参数的ParameterHandler 和处理结果集的ResultSetHandler。这两个对象都是在上面new 的时候创建的。

this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);

  这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。(对于详细的插件机制将在下篇博客中详细介绍)

StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);

  四大对象还有一个是谁?在什么时候创建的?(Executor)在第二步创建会话的时候创建的。用new 出来的StatementHandler 创建Statement 对象——prepareStatement()方法对语句进行预编译,处理参数。执行的StatementHandler 的query()方法,RoutingStatementHandler 的query()方法。

delegate 委派,最终执行PreparedStatementHandler 的query()方法。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}

  执行PreparedStatement 的execute()方法,后面就是JDBC 包中的PreparedStatement 的执行了。

  最后通过 resultSetHandler.handleResultSets(ps) 处理结果集。

  最后总结一下整个源码的过程所涉及的核心类:

mybatis(五)mybatis工作流程的更多相关文章

  1. Mybatis第一篇【介绍、快速入门、工作流程】

    什么是MyBatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为 ...

  2. springmvc 运行原理 Spring ioc的实现原理 Mybatis工作流程 spring AOP实现原理

    SpringMVC的工作原理图: SpringMVC流程 . 用户发送请求至前端控制器DispatcherServlet. . DispatcherServlet收到请求调用HandlerMappin ...

  3. MyBatis 工作流程及插件开发

    1. MyBatis 框架分层架构 2. MyBatis 工作流程 获取 SqlSessionFactory 对象: 解析配置文件(全局映射,Sql映射文件)的每一个信息,并保存在Configurat ...

  4. Mybatis的工作流程

    MyBatis工作流程 1:加载配置文件(mybatis-config.xml . *...Mapper.xml)并初始化, 将SQL的配置信息加载成为一个个MappedStatement对象(包括了 ...

  5. MyBatis的几个重要概念和工作流程

    MyBatis 几个重要的概念 Mapper 配置: Mapper 配置可以使用基于 XML 的 Mapper 配置文件来实现,也可以使用基于 Java 注解的 MyBatis 注解来实现,甚至可以直 ...

  6. SpringMVC的工作流程?Mybatis和hibernate区别?

    SpringMVC的工作流程?1. 用户发送请求至前端控制器DispatcherServlet2. DispatcherServlet收到请求调用HandlerMapping处理器映射器.3. 处理器 ...

  7. Mybatis工作流程及其原理与解析

    Mybatis简介:    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBat ...

  8. 关于MyBatis的工作流程

    1.从一个jdbc程序开始 public static void main(String[] args) { Connection connection = null; PreparedStateme ...

  9. 关于MyBatis的工作流程和与JDBC的比较与Hibernate的比较

    一.传统的JDBC的方式 1.从一个jdbc程序开始 public static void main(String[] args) { Connection connection = null; Pr ...

随机推荐

  1. Vue使用Ref跨层级获取组件实例

    目录 Vue使用Ref跨层级获取组件实例 示例介绍 文档目录结构 安装vue-ref 根组件自定义方法[使用provide和inject] 分别说明各个页面 结果 Vue使用Ref跨层级获取组件实例 ...

  2. scrapy的大文件下载(基于一种形式的管道类实现)

    scrapy的大文件下载(基于一种形式的管道类实现) 爬虫类中将解析到的图片地址存储到item,将item提交给指定的管道 在管道文件中导包:from scrapy.pipelines.images ...

  3. (05)-Python3之--运算符操作

    1.算数运算 num_a = 100 num_b = 5000 # 加法 + print(num_a + num_b) # 减法 - print(num_a - num_b) # 乘法 * print ...

  4. Java并发组件二之CyclicBarriar

    使用场景: 多个线程相互等待,直到都满足条件之后,才能执行后续的操作.CyclicBarrier描述的是各个线程之间相互等待的关系. 使用步骤: 正常实例化:CyclicBarrier sCyclic ...

  5. 在ubuntu编写helloworld

    安装vim 打开终端 输入sudo apt-get install vim-gtk 输入登陆密码 等待安装完成 编译C 创建.c文件:vim helloworld.c 编写代码,保存并退出 编译:gc ...

  6. mysql、sql server、oracle大比较

    MYSQL 多个数据库多个用户形式(最好每个数据库对应一个用户),占用内存小,适用于所有平台,开源免费 客户端和命令窗口,都是由数据库决定内容-> use datebase; 组函数在selec ...

  7. python 基础学习3 列表和元组 、字符串

    作为小白,坚持每日写学习记录,是督促坚持学习的动力, 今天主要是学习 列表和元组,列表是可以修改的,元组是不可变的.列表和元组的索引都是从0开始 列表可以修改, 可以对列表进行赋值,修改移除等各种方法 ...

  8. react空标签之The React Fragment

    如何使用React.Fragment创建不可见的HTML标签 在研究Ant Design Pro项目中,在登录模块中,有React.Fragment的实际应用 接下来先看一个小demo,将返回值包装在 ...

  9. Spark Dataset DataFrame空值null,NaN判断和处理

    Spark Dataset DataFrame空值null,NaN判断和处理 import org.apache.spark.sql.SparkSession import org.apache.sp ...

  10. MapReduce编程练习(四),统计多个输入文件学生的平均成绩,

    问题描述: 在输入文件中,有多个,其中每个输入文件代表一个学生的各科成绩,其中每行的数据形式为<科目,成绩>,你需要将每个文件中的每科目的成绩进行统计,然后求平均值. 输入文件格式: 这里 ...