> 文档中心 > Mybatis执行流程分析

Mybatis执行流程分析

文章目录

  • 前言
  • 一、演示DEMO
  • 流程分析
    • 读取mybatis的配置文件
    • 获取SqlSession工厂
    • 获取SqlSession
    • 设置操作的Mapper文件
  • Mapper操作
  • 总结

前言

对于Mybatis大家应该都很熟悉了,记得刚开始学Java的时候持久层两大框架Mybatis和Hibernate,当时我就比较喜欢Mybatis,Hibernate使用的不多,因为Mybatis的SQL语句比较自由,条理性比较清晰,当然不是说Hibernate不好,而是个人比较喜欢那种文件SQL清晰的风格,本文带大家简单回顾一下Mybatis。

一、演示DEMO

下面快速构建一个mybatis项目,数据库使用的MySQL8.0。

<dependency>   <groupId>org.mybatis</groupId>    <artifactId>mybatis</artifactId>    <version>3.3.0</version></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.16</version></dependency>

创建一个user表来做项目的例子,并且创建对应的实体类、Mapper接口和Mapper.xml,创建类就不写了。

CREATE TABLE `test001`.`user`  (  `id` bigint(0) NOT NULL,  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `age` int(0) NULL DEFAULT NULL,  PRIMARY KEY (`id`) USING BTREE) 

首先是Mybatis的配置文件

<configuration>    <environments default="development"> <environment id="development">     <transactionManager type="JDBC"/>     <dataSource type="POOLED">  <property name="driver" value="com.mysql.cj.jdbc.Driver"/>  <property name="url" value="jdbc:mysql://82.157.xx.xx:3306/test001?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"/>  <property name="username" value="root"/>  <property name="password" value=""/>     </dataSource> </environment>    </environments>    <mappers> <mapper resource="mappers/UserMapper.xml"/>    </mappers></configuration>

写个Demo类测试一下:

public static void main(String[] args) throws IOException {      Reader reader = Resources.getResourceAsReader("mybatis_config.xml");      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);      SqlSession sqlSession = sqlSessionFactory.openSession();      UserMapper mapper = sqlSession.getMapper(UserMapper.class);      UserEntity user = mapper.getUser(2);      System.out.println(user);}

OK简易版本的演示环境搭建完成
Mybatis执行流程分析

流程分析

看上面的示例一个简单的mybatis需要这么几步操作:

  1. 读取mybatis的配置文件。
  2. 获取SqlSession工厂。
  3. 获取SqlSession。
  4. 设置操作的Mapper文件。
  5. 开始需要的Mapper操作。

读取mybatis的配置文件

读取mybatis的配置文件是比较简单的,就是一个IO流操作。

 public static Reader getResourceAsReader(String resource) throws IOException {      InputStreamReader reader;      if (charset == null) {   reader = new InputStreamReader(getResourceAsStream(resource));      } else {   reader = new InputStreamReader(getResourceAsStream(resource), charset);      }      return reader;  }

获取SqlSession工厂

SqlSessionFactoryBuilder这是用来创建SqlSessionFactory的,这是使用的建造者模式。
直接看build方法:

   public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { //获取解析XML文件     XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);     //获取SqlSession工厂     var5 = this.build(parser.parse()); } catch (Exception var14) {     throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally {     ErrorContext.instance().reset();     try {  reader.close();     } catch (IOException var13) {     } } return var5;    }

XML解析的parse()方法最终的操作结果是把XML中读取的信息实例化,转换为Java对象Configuration类来存放,因为在config.xml配置文件中有mapper.xml设置,所以mapper.xml的信息也已经加载到了Configuration中。

 private void parseConfiguration(XNode root) {      try {   this.propertiesElement(root.evalNode("properties"));   this.typeAliasesElement(root.evalNode("typeAliases"));   this.pluginElement(root.evalNode("plugins"));   this.objectFactoryElement(root.evalNode("objectFactory"));   this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));   this.reflectionFactoryElement(root.evalNode("reflectionFactory"));   this.settingsElement(root.evalNode("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);      }  }

最后通过配置类构造了一个默认的SqlSession工厂类DefaultSqlSessionFactory,并把配置信息保存在了工厂中。

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

获取SqlSession

openSession最终走了下面的方法

//ExecutorType有三种SIMPLE,REUSE,BATCH,分别对应这三种不同的执行器,我们没有配置默认SIMPLE。后两个参数没有设置默认null和falseprivate 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);   //获取执行器   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;}

设置操作的Mapper文件

正常情况下接口是不能实例化的除非有子类,这里的Mapper文件实例化了,这说明它产生了子类或代理类,这里采用的代理模式。看源码中最终走到了这个方法MapperProxyFactory,对于Proxy单词大家应该很熟悉,代理的意思,这是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);  }     } }

看Mapper的代理,对于InvocationHandler应该很熟悉了吧,这是典型的JDK动态代理。
Mybatis执行流程分析
还记得JDK动态代理的时候怎么输出代理类吧,在Test Main()方法开始之前加一个JDK动态代理类输出设置。

System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);

看一下生成的代理类,因为UserMapper有了子类,所以我们可以实例化对象了。

public final class $Proxy0 extends Proxy implements UserMapper {    private static Method m1;    private static Method m3;    private static Method m2;    private static Method m0;    public $Proxy0(InvocationHandler var1) throws  { super(var1);    }    public final UserEntity getUser(int var1) throws  { try {     return (UserEntity)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) {     throw var3; } catch (Throwable var4) {     throw new UndeclaredThrowableException(var4); }    }...删除省略了部分代码}

Mapper操作

上面知道了生成Mapper使用的是代理设计模式,接下来就好说了,我们可以去invoke()方法去打断点拦截方法的执行。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {      if (Object.class.equals(method.getDeclaringClass())) {   try {return method.invoke(this, args);   } catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);   }      } else {   MapperMethod mapperMethod = this.cachedMapperMethod(method);   return mapperMethod.execute(this.sqlSession, args);      }  }

方法的具体执行,看是不是很熟悉了,这与我们之前用的JDBC是不是很相似。

 public Object execute(SqlSession sqlSession, Object[] args) {      Object param;      Object result;      if (SqlCommandType.INSERT == this.command.getType()) {   param = this.method.convertArgsToSqlCommandParam(args);   result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));      } else if (SqlCommandType.UPDATE == this.command.getType()) {   param = this.method.convertArgsToSqlCommandParam(args);   result = this.rowCountResult(sqlSession.update(this.command.getName(), param));      } else if (SqlCommandType.DELETE == this.command.getType()) {   param = this.method.convertArgsToSqlCommandParam(args);   result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));      } else if (SqlCommandType.SELECT == this.command.getType()) {   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 {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);   }      } else {   if (SqlCommandType.FLUSH != this.command.getType()) {throw new BindingException("Unknown execution method for: " + this.command.getName());   }   result = sqlSession.flushStatements();      }      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;      }  }

示例代码是SELECT类型的,SELECT ONE操作,看其最终还是查询的列表返回的第一个,如果有多个这个异常大家应该很熟悉了。

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

后面就不继续往里面看了,了解JDBC等操作的应该都清楚,里面就开始获取SQL语句使用?占位符替换的方式执行SQL语句了。最后将返回的内容转载成想要的类。

总结

最终我们写的demo例子按照这么个逻辑来执行的,当然这是分析的比较简单而且侧重点不同最终出来的流程图。
在这里插入图片描述

内容来源:蚂蚁课堂