MyBatis核心源码解析(一)

​MyBatis核心源码解析(一)

 

在前段时间,我们通过二十多个章节对SpringFramework的部分内容做了深度分析,并对核心部分的源码做了详细介绍,本章开始将对MyBatis的核心源码做深度分析,MyBatis学习将分为几个阶段,一是MyBatis单独源码做分析,二是对MyBatis扩展做分析,三是Spring整合MyBatis做分析,但是笔者在Spring的章节为了让大家能够理解Spring拓展的特性,已经对Spring整合MyBatis的核心设计思想做了深入的分析,因此有部分内容会简要介绍,只会针对其他一些整合特性做详细分析。

MyBatis框架搭建

一、mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <!--   <plugins>        <plugin interceptor="com.SQLInterceptor">            <property name="better" value="better" />        </plugin>        <plugin interceptor="com.MyPageInterceptor">        </plugin>    </plugins>-->    <!-- 和Spring整合后environment配置都会被干掉 -->    <environments default="development">        <environment id="development">            <!-- 使用jdbc事务管理,目前由mybatis来管理 -->            <transactionManager type="JDBC" />            <!-- 数据库连接池,目前由mybatis来管理 -->            <dataSource type="POOLED"><!--有关于mysql数据库的各种信息-->                <property name="driver" value="com.mysql.jdbc.Driver" />                <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />                <property name="username" value="root" />                <property name="password" value="root" />            </dataSource>        </environment>    </environments>    <mappers>        <!--将操作配置文件User.xml系添加进mapper-->        <mapper resource="UserMapper.xml" />    </mappers></configuration>

 

二、UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.UserMapper"> <!-- 注意,因为这边没有用到mapper接口,所以这里的namespace不需要是完全的类名 -->    <!-- 通过id查询用户 -->    <select id="findUserById" parameterType="int" resultType="com.User">        select * from user where  id = #{id}    </select></mapper>

三、Mapper接口

package com;import java.util.List;public interface UserMapper {    List<User> findUserById(int id);}

四、实体类

package com;public class User {    private int id;    private String name;    private int age;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}

五、测试类

import com.User;import com.UserMapper;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.mybatis.spring.SqlSessionTemplate;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.io.IOException;import java.io.InputStream;import java.util.List;import java.util.stream.Collectors;public class Test {    public static void main(String[] args){   /*     ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring-config.xml");        UserMapper userMapper= (UserMapper) applicationContext.getBean("userMapper");        userMapper.findUserById(1);*/            try {                InputStream inputStream= Resources.getResourceAsStream("mybatis.xml");                // 创建sqlSessionFactory                SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);                // 根据sqlSessionFactory获取sqlSession                SqlSession sqlSession = sqlSessionFactory.openSession();                // 这里采用了两种方式,一种是通过接口的方式,另外一种是直接使用sqlSession对象调用对应的方法                List<User> users= sqlSession.selectList("findUserById",2);                for (User user : users) {                    System.out.println("Age : "+user.getAge()+"; Name : "+user.getName()+                            "; Id : "+user.getId());                }                System.out.println("---------------------------");                                UserMapper userMapper=sqlSession.getMapper(UserMapper.class);                List<User> list = userMapper.findUserById(1);                for (User user : list) {                    System.out.println("Age : "+user.getAge()+"; Name : "+user.getName()+                            "; Id : "+user.getId());                }            } catch (IOException e) {                e.printStackTrace();            }    }}

然后sql数据

 

运行结果

"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.5\lib\idea_rt.jar=53209:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.5\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;C:\Users\HP\IdeaProjects\Spring\Mybatis\target\classes;C:\Users\HP\.m2\repository\org\springframework\spring-webmvc\5.1.3.RELEASE\spring-webmvc-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-beans\5.1.3.RELEASE\spring-beans-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-context\5.1.3.RELEASE\spring-context-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-core\5.1.3.RELEASE\spring-core-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-jcl\5.1.3.RELEASE\spring-jcl-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-expression\5.1.3.RELEASE\spring-expression-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-web\5.1.3.RELEASE\spring-web-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-aop\5.1.3.RELEASE\spring-aop-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-tx\5.1.3.RELEASE\spring-tx-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-test\5.1.3.RELEASE\spring-test-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-jdbc\5.1.3.RELEASE\spring-jdbc-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\springframework\spring-aspects\5.1.3.RELEASE\spring-aspects-5.1.3.RELEASE.jar;C:\Users\HP\.m2\repository\org\aspectj\aspectjweaver\1.9.2\aspectjweaver-1.9.2.jar;C:\Users\HP\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.9.7\jackson-core-2.9.7.jar;C:\Users\HP\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.9.7\jackson-databind-2.9.7.jar;C:\Users\HP\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.9.7\jackson-annotations-2.9.7.jar;C:\Users\HP\.m2\repository\org\mybatis\mybatis-spring\2.0.0\mybatis-spring-2.0.0.jar;C:\Users\HP\.m2\repository\org\mybatis\mybatis\3.5.1\mybatis-3.5.1.jar;C:\Users\HP\.m2\repository\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar;C:\Users\HP\.m2\repository\com\mchange\c3p0\0.9.5.2\c3p0-0.9.5.2.jar;C:\Users\HP\.m2\repository\com\mchange\mchange-commons-java\0.2.11\mchange-commons-java-0.2.11.jar;C:\Users\HP\.m2\repository\org\mybatis\generator\mybatis-generator-core\1.3.5\mybatis-generator-core-1.3.5.jar;C:\Users\HP\.m2\repository\com\github\pagehelper\pagehelper\5.0.0\pagehelper-5.0.0.jar;C:\Users\HP\.m2\repository\com\github\jsqlparser\jsqlparser\0.9.5\jsqlparser-0.9.5.jar;C:\Users\HP\.m2\repository\com\alibaba\druid\1.1.14\druid-1.1.14.jar;C:\Users\HP\.m2\repository\jstl\jstl\1.2\jstl-1.2.jar;C:\Users\HP\.m2\repository\junit\junit\4.12\junit-4.12.jar;C:\Users\HP\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\HP\.m2\repository\cglib\cglib\3.2.5\cglib-3.2.5.jar;C:\Users\HP\.m2\repository\org\ow2\asm\asm\5.2\asm-5.2.jar;C:\Users\HP\.m2\repository\org\apache\ant\ant\1.9.6\ant-1.9.6.jar;C:\Users\HP\.m2\repository\org\apache\ant\ant-launcher\1.9.6\ant-launcher-1.9.6.jar" TestAge : 123; Name : dasdada; Id : 1---------------------------Age : 23; Name : 32132321sdsda; Id : 2Process finished with exit code 0

 

可以看到这两种方式都能实现,我们先关注第一种方式,也就是直接通过SqlSession这个接口去操作数据库,我们先看一下这个接口

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package org.apache.ibatis.session;import java.io.Closeable;import java.sql.Connection;import java.util.List;import java.util.Map;import org.apache.ibatis.cursor.Cursor;import org.apache.ibatis.executor.BatchResult;public interface SqlSession extends Closeable {    <T> T selectOne(String var1);    <T> T selectOne(String var1, Object var2);    <E> List<E> selectList(String var1);    <E> List<E> selectList(String var1, Object var2);    <E> List<E> selectList(String var1, Object var2, RowBounds var3);    <K, V> Map<K, V> selectMap(String var1, String var2);    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);    <T> Cursor<T> selectCursor(String var1);    <T> Cursor<T> selectCursor(String var1, Object var2);    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);    void select(String var1, Object var2, ResultHandler var3);    void select(String var1, ResultHandler var2);    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);    int insert(String var1);    int insert(String var1, Object var2);    int update(String var1);    int update(String var1, Object var2);    int delete(String var1);    int delete(String var1, Object var2);    void commit();    void commit(boolean var1);    void rollback();    void rollback(boolean var1);    List<BatchResult> flushStatements();    void close();    void clearCache();    Configuration getConfiguration();    <T> T getMapper(Class<T> var1);    Connection getConnection();}

可以看到 这里的看到这个sqlsession接口包含了我们所需要的所有操作数据库的功能,没错这个接口是MyBatis的核心类,它就是操作数据库的核心,好了,现在我们来跟踪一下源码,首先第一步是

// 创建sqlSessionFactorySqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);

其次我们主要关注build方法,点进去

然后再跟踪

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {    try {      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);      return build(parser.parse());    } catch (Exception e) {      throw ExceptionFactory.wrapException("Error building SqlSession.", e);    } finally {      ErrorContext.instance().reset();      try {        inputStream.close();      } catch (IOException e) {        // Intentionally ignore. Prefer previous error.      }    }  }

这个XMLConfigBuilder 我们可以简单理解成一个xml管理容器,这里对XMLConfigBuilder 进行实例化,随后执行     return build(parser.parse());这里我们就要注意了,先进入parse()方法

然后跟踪parseConfiguration(parser.evalNode("/configuration"));

private void parseConfiguration(XNode root) {    try {      //issue #117 read properties first      propertiesElement(root.evalNode("properties"));      Properties settings = settingsAsProperties(root.evalNode("settings"));      loadCustomVfs(settings);      loadCustomLogImpl(settings);      typeAliasesElement(root.evalNode("typeAliases"));      pluginElement(root.evalNode("plugins"));      objectFactoryElement(root.evalNode("objectFactory"));      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));      reflectorFactoryElement(root.evalNode("reflectorFactory"));      settingsElement(settings);      // read it after objectFactory and objectWrapperFactory issue #631      environmentsElement(root.evalNode("environments"));      databaseIdProviderElement(root.evalNode("databaseIdProvider"));      typeHandlerElement(root.evalNode("typeHandlers"));      mapperElement(root.evalNode("mappers"));    } catch (Exception e) {      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);    }  }

从名字我们应该可以看出,这里就是对Mybatis的配置文件进行解析,这里包含了properties、plugins等等,其中包含了environments和mappers,这里我们回到xml配置文件就知道了

通过配置文件我们就能够对应上这里到底是什么,我们将代码跑完,然后看看这个root到底扫描了那些东西

将这个root内容复制出来

<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="UserMapper.xml"/></mappers></configuration>

可以看到这里就解析出来了我们配置的信息,好了,这里我们再回到mapperElement(root.evalNode("mappers"));进入这个方法

注意这里的mapper,然后前面都是一些取值、赋值,不用管,直接找到

其实大家也猜得到它要干嘛了,既然已经知道了Mapper类以及它的一些基本信息,那么久开始对它的基本信息解析了,里面其实也就是对我们的Mapper解析并绑定在一个Configuration类里面,这里就不继续跟踪了,我讲部分重点截图下来

那么对到我们的test类,

  // 创建sqlSessionFactorySqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);

这里的sqlSessionFactory 我们就创建成功了,现在我们再来看第二句代码

 // 根据sqlSessionFactory获取sqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();

然后我们跟踪进去

  @Override  public SqlSession openSession() {    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);  }

再跟踪进去

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {    Transaction tx = null;    try {      final Environment environment = configuration.getEnvironment();      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);      final Executor executor = configuration.newExecutor(tx, execType);      return new DefaultSqlSession(configuration, executor, autoCommit);    } catch (Exception e) {      closeTransaction(tx); // may have fetched a connection so lets call close()      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);    } finally {      ErrorContext.instance().reset();    }  }

 

这里会根据我们前面定义的Configuration去获取一个Environment

这里就是我们配置的数据源的Environment标签,通过数据源创建事务,然后创建了Executor,我们先不关注这个Executor是干什么的,继续看代码,最后就带着这些创建的对象返回了一个SqlSession,好了我们获取了SqlSession,现在我们来看我们的SqlSession能做什么事情,

其实可以看到,它基本上包含了所有执行数据库的方法,也就是说我们拿到了SqlSession,我们就可以操作数据库。下面我们看实例代码。

// 这里采用了两种方式,一种是通过接口的方式,另外一种是直接使用sqlSession对象调用对应的方法List<User> users= sqlSession.selectList("findUserById",2);

我们调试进去

然后这里会调到

然后这里会知道我们对应的我们初始化的mapper

关键的是下一段代码,

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

可以看到这里出现了executor,那么我们既然出现了它,就说明Sqlsession它只是通过执行器代理做事,真正做事的是执行器,我们再进一步深入

然后先是拼接sql,然后再是创建缓存,然后执行到

 query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

我们在深入进去,

再次获取缓存是否存在,如果不存在就调用查询方法

  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 (closed) {      throw new ExecutorException("Executor was closed.");    }    if (queryStack == 0 && ms.isFlushCacheRequired()) {      clearLocalCache();    }    List<E> list;    try {      queryStack++;      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;      if (list != null) {        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);      } else {        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);      }    } finally {      queryStack--;    }    if (queryStack == 0) {      for (DeferredLoad deferredLoad : deferredLoads) {        deferredLoad.load();      }      // issue #601      deferredLoads.clear();      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {        // issue #482        clearLocalCache();      }    }    return list;  }

前面都是做一些缓存工作,我们直接看到

  list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

进入后调用

 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

这里MyBatis自己封装了一个StatementHandler,而这个类其实才是真正底层操作数据库的,我们一直调用下去最终我们会

可以看到这里的PreparedStatement,这里的就是java.sql的api了,就不做细节分析,然后我们进入resultSetHandler.handleResultSets(ps);然后我们一直深入找到下面这个类,请注意,此时我们已经执行了sql了,现在是要将数据库返回的东西转换成我们的对象,

然后发现此时的数据并没有赋值,我们执行下去

也就是说foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;这个方法十分的关键。

然后看到第一段代码List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);我们进入找到这段代码

可以看到,这里出现了TypeHandler,我们看看这个接口

可以看到Mybatis自己实现了一套数据转换的处理器,我们在Spring中也提到过数据转换,那么为什么   Mybatis要搞一套自己的转换器呢?这道理也很简单,因为我们的sql字段和java类型差距较大,所以不能直接用Spring提供的转换器,因此Mybatis就自己实现了一套转换机制。那么这里是如何选择自己的转换器的呢?

这里也是根据参数类型直接返回,有了这个转换器就可以直接实现数据转换了,就可以解答我刚才提出的问题。好了,我们现在对Mybatis的第一种获取方式做了详细的介绍

 List<User> list = userMapper.findUserById(1);for (User user : list) {           System.out.println("Age : "+user.getAge()+"; Name : "+user.getName()+              "; Id : "+user.getId());}

这种Mapper方式就在下个章节续写吧。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值