Mybatis执行流程分析


前言

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

一、演示DEMO

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

<!-- mybatis使用的是3.3 -->
<dependency>
   <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.3.0</version>
</dependency>
<!-- mysql驱动包 -->
<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&amp;useSSL=false&amp;serverTimezone=UTC&amp;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需要这么几步操作:

  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和false
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);
          //获取执行器
          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动态代理。
在这里插入图片描述
还记得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例子按照这么个逻辑来执行的,当然这是分析的比较简单而且侧重点不同最终出来的流程图。
在这里插入图片描述

内容来源:蚂蚁课堂

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值