1.Mybatis的使用

public static void main(String[] args) throws IOException {
//1.获取配置文件流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//2.构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.获取mapper
UnitMapper mapper = sqlSession.getMapper(UnitMapper.class);
//5.执行增删改查
Unitinfo_source unitinfo_source = mapper.selectById(1);
System.out.println(unitinfo_source);
}

这是我们在简单使用mybatis时的代码,并未整合Spring,首先来看下Mybatis具体的执行流程是什么。后续我们再看整合Spring后Mybatis做了什么处理。以及Springboot中对Mybatis做了怎样的处理。

2.执行流程

第一步:通过Resources获取mybatis配置文件的输入流

Resources是Mybatis下的一个类,getResourceAsStream读取到mybatis的配置文件,以流的形式加载进来

第二步:构建SqlSessionFactory

 1     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
2 SqlSessionFactory var5;
3 try {
4 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
5 var5 = this.build(parser.parse());
6 } catch (Exception var14) {
7 throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
8 } finally {
9 ErrorContext.instance().reset();
10
11 try {
12 inputStream.close();
13 } catch (IOException var13) {
14 }
15
16 }
17
18 return var5;
19 }

调用了XMLConfigBuilder的parse()方法,具体看下这个方法是干什么的

 1     public Configuration parse() {
2 if (this.parsed) {
3 throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4 } else {
5 this.parsed = true;
6 this.parseConfiguration(this.parser.evalNode("/configuration"));
7 return this.configuration;
8 }
9 }
10
11 private void parseConfiguration(XNode root) {
12 try {
13 this.propertiesElement(root.evalNode("properties"));
14 Properties settings = this.settingsAsProperties(root.evalNode("settings"));
15 this.loadCustomVfs(settings);
16 this.loadCustomLogImpl(settings);
17 this.typeAliasesElement(root.evalNode("typeAliases"));
18 this.pluginElement(root.evalNode("plugins"));
19 this.objectFactoryElement(root.evalNode("objectFactory"));
20 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
21 this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
22 this.settingsElement(settings);
23 this.environmentsElement(root.evalNode("environments"));
24 this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25 this.typeHandlerElement(root.evalNode("typeHandlers"));
26 this.mapperElement(root.evalNode("mappers"));
27 } catch (Exception var3) {
28 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
29 }
30 }

可以看到XMLConfigBuilder的parse()方法来解析配置文件的配置信息,可以看到,解析properties标签,settings,typeAliases别名等等一些信息,并返回一个Configuration;后续我们会看到,Configuration是Mybatis很重要的一个核心类,包括了很多执行过程中的上下文信息,我们这里的配置信息被加载到了这个类中

1     public SqlSessionFactory build(Configuration config) {
2 return new DefaultSqlSessionFactory(config);
3 }

通过调用SqlSessionFactory的build方法,返回了DefaultSqlSessionFactory对象,DefaultSqlSessionFactory是SqlSessionFactory的一个实现类

第三步:创建SqlSession

1     public SqlSession openSession() {
2 return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
3 }
 1     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
2 Transaction tx = null;
3
4 DefaultSqlSession var8;
5 try {
6 Environment environment = this.configuration.getEnvironment();
7 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
8 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
9 Executor executor = this.configuration.newExecutor(tx, execType);
10 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
11 } catch (Exception var12) {
12 this.closeTransaction(tx);
13 throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
14 } finally {
15 ErrorContext.instance().reset();
16 }
17
18 return var8;
19 }

首先获取到刚才从配置文件解析到事务,TransactionFactory创建出Transaction, 我们知道sql执行一般都会用到事务提交、回滚等操作。

创建Executor对象,executor是mybatis的核心执行器,用于执行增删改查操作。一二级缓存都在执行器中进行处理。

最后得到了SqlSession对象了,DefaultSqlSession是SqlSession的一个实现。

第四步:获取Mapper

调用SqlSession的getMapper方法

1     public <T> T getMapper(Class<T> type) {
2 return this.configuration.getMapper(type, this);
3 }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}

然后可以发现调用了Configuration中的getMapper方法,再调用MapperRegistry(注册mapper的类)获取Mapper

    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);
}
}
}

可以看到调用了mapperProxyFactory的newInstance方法

1     protected T newInstance(MapperProxy<T> mapperProxy) {
2 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
3 }
4
5 public T newInstance(SqlSession sqlSession) {
6 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
7 return this.newInstance(mapperProxy);
8 }

可以看到我们在SqlSession调用getMapper时,实际上是通过jdk的动态代理来实现的,通过Proxy.newProxyInstance来创建mapper代理对象

第五步:执行mapper的增删改查方法

通过第四步中,我们知道了getMapper获取到了代理对象,也就是说Proxy.newProxyInstance最后一个参数mapperProxy对象类必定实现了InvocationHandler的invoke方法,

 1     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
2 try {
3 if (Object.class.equals(method.getDeclaringClass())) {
4 return method.invoke(this, args);
5 }
6
7 if (method.isDefault()) {
8 if (privateLookupInMethod == null) {
9 return this.invokeDefaultMethodJava8(proxy, method, args);
10 }
11
12 return this.invokeDefaultMethodJava9(proxy, method, args);
13 }
14 } catch (Throwable var5) {
15 throw ExceptionUtil.unwrapThrowable(var5);
16 }
17
18 MapperMethod mapperMethod = this.cachedMapperMethod(method);
19 return mapperMethod.execute(this.sqlSession, args);
20 }

invoke方法中首先判断了是否是普通类或者判断是否是默认方法,如果是普通类,执行其默认方法;如果是默认defaut方法,执行相应的java8或java9相应方法(这个没具体了解,如果感兴趣可以自己深入了解);咱们主要是看19行, mapperMethod的excute方法

 1     public Object execute(SqlSession sqlSession, Object[] args) {
2 Object result;
3 Object param;
4 switch(this.command.getType()) {
5 case INSERT:
6 param = this.method.convertArgsToSqlCommandParam(args);
7 result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
8 break;
9 case UPDATE:
10 param = this.method.convertArgsToSqlCommandParam(args);
11 result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
12 break;
13 case DELETE:
14 param = this.method.convertArgsToSqlCommandParam(args);
15 result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
16 break;
17 case SELECT:
18 if (this.method.returnsVoid() && this.method.hasResultHandler()) {
19 this.executeWithResultHandler(sqlSession, args);
20 result = null;
21 } else if (this.method.returnsMany()) {
22 result = this.executeForMany(sqlSession, args);
23 } else if (this.method.returnsMap()) {
24 result = this.executeForMap(sqlSession, args);
25 } else if (this.method.returnsCursor()) {
26 result = this.executeForCursor(sqlSession, args);
27 } else {
28 param = this.method.convertArgsToSqlCommandParam(args);
29 result = sqlSession.selectOne(this.command.getName(), param);
30 if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
31 result = Optional.ofNullable(result);
32 }
33 }
34 break;
35 case FLUSH:
36 result = sqlSession.flushStatements();
37 break;
38 default:
39 throw new BindingException("Unknown execution method for: " + this.command.getName());
40 }
41
42 if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
43 throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
44 } else {
45 return result;
46 }
47 }

这个就是我们核心执行增删改查的方法,通过判断 sql的执行类型,执行相应的方法,返回相应的结果

也许有人有疑问,是从哪里解析到sql语句的呢,其实在解析配置文件的时候就已经将sql解析到Configuration类对象中了,我们看下源码

 1     private void parseConfiguration(XNode root) {
2 try {
3 this.propertiesElement(root.evalNode("properties"));
4 Properties settings = this.settingsAsProperties(root.evalNode("settings"));
5 this.loadCustomVfs(settings);
6 this.loadCustomLogImpl(settings);
7 this.typeAliasesElement(root.evalNode("typeAliases"));
8 this.pluginElement(root.evalNode("plugins"));
9 this.objectFactoryElement(root.evalNode("objectFactory"));
10 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11 this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
12 this.settingsElement(settings);
13 this.environmentsElement(root.evalNode("environments"));
14 this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
15 this.typeHandlerElement(root.evalNode("typeHandlers"));
16 this.mapperElement(root.evalNode("mappers"));
17 } catch (Exception var3) {
18 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
19 }
20 }

我们看到在16行,有this.mapperElement(root.evalNode("mappers"))来解析mapper节点信息

 1     private void mapperElement(XNode parent) throws Exception {
2 if (parent != null) {
3 Iterator var2 = parent.getChildren().iterator();
4
5 while(true) {
6 while(var2.hasNext()) {
7 XNode child = (XNode)var2.next();
8 String resource;
9 if ("package".equals(child.getName())) {
10 resource = child.getStringAttribute("name");
11 this.configuration.addMappers(resource);
12 } else {
13 resource = child.getStringAttribute("resource");
14 String url = child.getStringAttribute("url");
15 String mapperClass = child.getStringAttribute("class");
16 XMLMapperBuilder mapperParser;
17 InputStream inputStream;
18 if (resource != null && url == null && mapperClass == null) {
19 ErrorContext.instance().resource(resource);
20 inputStream = Resources.getResourceAsStream(resource);
21 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
22 mapperParser.parse();
23 } else if (resource == null && url != null && mapperClass == null) {
24 ErrorContext.instance().resource(url);
25 inputStream = Resources.getUrlAsStream(url);
26 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
27 mapperParser.parse();
28 } else {
29 if (resource != null || url != null || mapperClass == null) {
30 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
31 }
32
33 Class<?> mapperInterface = Resources.classForName(mapperClass);
34 this.configuration.addMapper(mapperInterface);
35 }
36 }
37 }
38
39 return;
40 }
41 }
42 }

通过源码我们可以看到在16行以下,XMLBuilderMapper的parse方法解析mapper的xml文件,在这个方法中会将sql解析。

其实在执行器执行的时候会根据解析到的id与sql的映射去拿到执行的SQL,再交由底层jdbc来执行相应的sql语句

Mybatis源码系列 执行流程(一)的更多相关文章

  1. mybatis源码阅读-执行一个sql的流程(九)

    图解 图片来源:https://my.oschina.net/zudajun/blog/670373 Mapper接口调用原理 我们整合成Spring  直接使用Mapper就能执行对应的sql 表现 ...

  2. MyBatis源码分析(五):MyBatis Cache分析

    一.Mybatis缓存介绍 在Mybatis中,它提供了一级缓存和二级缓存,默认的情况下只开启一级缓存,所以默认情况下是开启了缓存的,除非明确指定不开缓存功能.使用缓存的目的就是把数据保存在内存中,是 ...

  3. drf源码系列

    过滤器 对查询出来的数据进行筛选可写可不写 from rest_framework.filters import BaseFilterBackend 源码 ''' def filter_queryse ...

  4. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  5. mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例 1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下 执行完后,我们看下数据 ...

  6. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  7. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  8. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  9. MyBatis 源码分析 - SQL 的执行过程

    * 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...

随机推荐

  1. SpringBoot Logback 日志配置

    目录 前言 日志格式 日志输出 日志轮替 日志级别 日志分组 小结 前言 之前使用 SpringBoot 的时候,总是习惯于将日志框架切换为 Log4j2,可能是觉得比较靠谱,也可能年龄大了比较排斥新 ...

  2. day04 sersync实时同步和ssh服务

    day04 sersync实时同步和ssh服务 sersync实时同步 1.什么是实时同步 实时同步是一种只要当前目录发生变化则会触发一个事件,事件触发后会将变化的目录同步至远程服务器. 2.为什么使 ...

  3. git 的基本流程

    有个本地文件 打开 新建一个 打开git $ git push origin master 这里是上传文件.  (你每次上传的时候,都要先提交到本地的仓库...然后再上传) github上就有了 如何 ...

  4. 艺恩网内地总票房排名Top100信息及其豆瓣评分详情爬取

    前两天用python2写的一个小爬虫 主要实现了从http://www.cbooo.cn/Alltimedomestic这么个网页中爬取每一部电影的票房信息等,以及在豆瓣上该电影的评分信息 代码如下 ...

  5. 开源低代码开发平台entfrm2.1.0更新

    开源低代码开发平台entfrm2.1.0更新 新功能 代码生成支持主子表,支持预览: 新增多应用顶部菜单与左侧菜单联动: element-ui升级到2.15.1: 新增表单管理,集成avue-from ...

  6. shell条件测试语句实例-测试apache是否开启

    终于理解了shell条件测试语句"!="和"-n"的用法区别,于是有了如下的shell脚本,做为练习. 第一种方法:测试apache是否开启?字符串测试 #!/ ...

  7. 【Java基础】Java 注解详解

    对于Java注解,我之前的印象是很模糊的,总觉得这个东西经常听说,也经常用,但是具体是怎么回事,好像没有仔细学习过,说到注解,立马想到@Controller,仅此而已. 对于Java注解,我咨询过一些 ...

  8. 【Spring Framework】Spring入门教程(四)注册Bean到IOC容器

    注册Bean到IOC容器大致分为4种: ①.包扫描+组件注解(@Controller.@Service.@Repository.@Component) 针对类是我们自己编写的情况 ②.@Bean注解 ...

  9. Vue API 4 (过渡和动画)

    transition name 用于自动生成 CSS 过渡类名.例如:name: fade 将自动拓展为 .fade-enter ,.fade-enter-active等.默认类名为 "v& ...

  10. 【Java 调优】Java性能优化

    Java性能优化的50个细节(珍藏版) 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: ...