mybatis 源码简单探究

一 、初始化环境

中文网 :https://mybatis.net.cn

前提

  • maven环境
  • jdk1.8
  • mysql8.0

pom.xml

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.11</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>



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.wuxin.mybatis.read.mapper.UserMapper">
    <insert id="addUser" parameterType="java.lang.String">
        insert into `user` (`username`, `address`)
        values (#{username}, #{address})
    </insert>
    <update id="updateId" >
        update `user` set `username`=#{username} ,address=#{address} where userId=#{userId}
    </update>
    <select id="selectOne" resultType="com.wuxin.mybatis.read.pojo.User" parameterType="java.lang.Long">
        select *
        from user
        where userId = #{userId}
    </select>
    <select id="list" resultType="com.wuxin.mybatis.read.pojo.User">
        select * from user;
    </select>
</mapper>

mybatis-config.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>
    <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://localhost:3306/mybatis-test?serverTimezone=UTC&amp;character=utf8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

二、执行流程

1、配置环境初始化

// main

// ...
String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new   SqlSessionFactoryBuilder().build(inputStream);
// ...

通过 org.apache.ibatis.builder.XMLConfigBuilder 类初始化环境,解析 xxx.xml文件生成对应Java对象

    // org.apache.ibatis.builder.XMLConfigBuilder
 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        // ...
        this.parser = parser;
    }

    // 配置文件解析只能调用一次,下次调用抛出异常!!!
    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;
        }
    }

   // 该方法通过
    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"));
            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);
        }
    }
// 通过上面节点解析顺序知道,节点位置顺序不能乱,最好参考上面这个配置的节点顺序

SqlSessionFactoryBuilder 调用 XMLConfigBuilder 的 parse 方法,解析生成 config 对象配置

  //   org.apache.ibatis.session.SqlSessionFactoryBuilder build();
  // ...
  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  var5 = this.build(parser.parse());

Configuration ,只有两个构造器

   // 有参构造
   public Configuration(Environment environment) {
        this();
        // 可以通过xml配置指定环境 
        this.environment = environment;
    }

   // 无参构造
   // 默认初始化环境 通过xml配置读取过程中,如果没有指定对应配置,采用如下默认配置
   // 关于下面配置详情参考 https://mybatis.net.cn/configuration.html
    public Configuration() {
        // 初始化值
        // ... 详细配置参考 org.apache.ibatis.session.Configuration 类的构造函数
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        // ... 默认配置文件
        this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        // ...
    }

更多信息参考下图,

执行流程如下:

  1. XMLConfigBuilder 处理配置类 集中管理
  2. XMLMapperBuilder 处理 xxxxMapper.xml 扫描 xxxxMapper.xml 等
  3. MapperBuilderAssistant mapper辅助类
  4. XMLStatementBuilder statement生成
  5. XMLScriptBuilder 处理 xml 中的 脚本
  6. SqlSourceBuilder 生成sql 语句
  7. ParameterMappingTokenHandler 将参数 #{} 替换为 ?,并保存参数信息

2、整体执行流程

mybatis基本执行流程

3、查询详细执行流程

详细执行流程

4、关于各个处理器,执行器说明

1、SqlSession

会话管理,默认是 DefaultSqlSession

2、Executor

执行器:

  1. CachingExecutor
    • 二级缓存
  2. BaseExecutor 一级缓存,如果没有一级缓存,执行数据库操作,如果是修改操作,删除缓存
    • SimpleExecutor 一般查询或者修改处理器
    • BatchExecutor 批处理

3、StatementHandlder

注意

RoutingStatementHandler 最终还是生成 PreparedStatementHandler 、SimpleStateHandler 和

CallableStatementHandler三个中的一个

4、ParameterHandlder

参数解析,将?替换为输入的参数,然后通过 Preparement 执行exqute() 执行操作

三 、缓存

1、二级缓存

注意二级缓存执行在一级缓存之前

二级缓存实现类是 org.apache.ibatis.executor.CachingExecutor

二级缓存以实现核心部分

public class CachingExecutor implements Executor {
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }

    // ....

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        // 获取 mapper 的二级缓存
        if (cache != null) {
                // ...
                // 获取内容
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    // 如果结果为空,从执行器中获取 一般是 BaseExecutor
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }
 		// 如果获取到内容直接返回
                return list;

        }
        // 如果没有获取到内容 交给下一个执行器执行 一般是 BaseExecutor 责任链模式
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

   // ....
}

2、一级缓存

PerpetualCache 继承了 cache 并且作为 一级缓存,存储方式是 HashMap

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
       // ....
         try {
                ++this.queryStack;
                // 一级缓存
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    // 缓存校验
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    // 从数据库中获取
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }
            // ...
            return list;
        }
    }

3、缓存 key 是怎么来的?

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

添加 cachekey

    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            CacheKey cacheKey = new CacheKey();
            cacheKey.update(ms.getId());
            cacheKey.update(rowBounds.getOffset());
            cacheKey.update(rowBounds.getLimit());
            cacheKey.update(boundSql.getSql());
            // ....
            return cacheKey;
        }
    }

默认org.apache.ibatis.cache.CacheKey重写了toString

public String toString() {
        StringJoiner returnValue = new StringJoiner(":");
        returnValue.add(String.valueOf(this.hashcode));
        returnValue.add(String.valueOf(this.checksum));
        Stream var10000 = this.updateList.stream().map(ArrayUtil::toString);
        Objects.requireNonNull(returnValue);
        // lambda表达式 执行 StringJoiner.add()
        // toString() 拼装之后返回
        // StringJoiner add 实际上执行的是 StringBuilder.append(xxxx)
        var10000.forEach(returnValue::add); 
        return returnValue.toString();
    }


// ...



关于id的部分源代码

org.apache.ibatis.binding.MapperMetho$SqlCommand

 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
       String statementId = mapperInterface.getName() + "." + methodName;
             // ....
 }

例如:

实例key

-8238651:-329230814:com.wuxin.mybatis.read.mapper.UserMapper.list:0:2147483647:select * from user;:development
  • MappedStatement

    • id ,为 namespace + idName, 实际上就是mapper的命名空间和 id名
    • 例如 com.wuxin.mybatis.read.mapper.UserMapper+list
  • Object

  • RowBounds 分页对象

  • 默认为 RowBounds.DEFAULT

  • ResultHandler 结果处理器

  • 默认为 Executor.NO_RESULT_HANDLER

4、修改和查询

  • 查询
    • 执行 executor 中 doQuery()
  • 修改
    • 添加
    • 删除
    • 修改
    • 执行 executor 中 doUpdate()

查询

如果走到这一步了说明缓存中没有结果

   // org.apache.ibatis.executor.BaseExecutor

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 {
            // 从数据库中执行查询操作
            // 该方法在 BaseExecutor为抽象方法 ,交给实现类执行(四种)如下图
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 删除之前对应的key
            this.localCache.removeObject(key);
        }
        // 存入 缓存中
        this.localCache.putObject(key, list);
        // ...
        return list;
    }

交给下一个执行器执行

最终执行的是

  
// 处理多个结果
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
    }

// 处理单个结果
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleCursorResultSets(ps);
    }

修改

修改包括添加、删除和修改操作

 // org.apache.ibatis.executor.BaseExecutor
public int update(MappedStatement ms, Object parameter) throws SQLException {
      // ...
      // 每次执行修改操作之前,删除本地缓存
     this.clearLocalCache();
     return this.doUpdate(ms, parameter);
 }

修改操作最终执行的是 PreparedStatementHandler 的 update() 方法

  public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        int rows = ps.getUpdateCount();
        // ...
        return rows;
    }

四、插件实现

默认查询分页的经过 DefaultSqlSession方法 分页设置默认参数 RowBounds.DEFAULT


    public <E> List<E> selectList(String statement, Object parameter) {
        // 如果没有 RowBounds 对象,系统默认 RowBounds.DEFAULT
        // RowBounds.DEFAULT ======>  offset=0 ,limit= Integer.MAX_VALUE
        return this.selectList(statement, parameter, RowBounds.DEFAULT); //
    }

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
    }

有关 RowBounds

Public class RowBounds {
    public static final int NO_ROW_OFFSET = 0;
    public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
    public static final RowBounds DEFAULT = new RowBounds(); 
    private final int offset;
    private final int limit;

    public RowBounds() {
        this.offset = 0;
        this.limit = Integer.MAX_VALUE;
    }
    public RowBounds(int offset, int limit) {
        this.offset = offset;
        this.limit = limit;
    }
    // .... getter methods
}

所以list 最多能够查询结果 Integer.MAX_VALUE,经过拼接 的sql语句

select c1,c2,...,cn from tbName xxxxx limit 0,Integer.MAX_VALUE

更多详情参考 :https://mybatis.net.cn/configuration.html#plugins

五、SQL生成

1、条件处理

mybatis 中 封装了许多对应 mapper中各种各样的条件

核心 SqlNode是 TrimSqlNode

public class TrimSqlNode implements SqlNode {
    // ...
}

2、"#{ }"是如何 替换成 "?"

通过 GenericTokenParser 的方法 parse()

    public String parse(String text) {
       // ...
        do {
            // ...
            // 关键替换
            // 参考下面步骤 SqlSourceBuilder 类 的  ParameterMappingTokenHandler
            builder.append(this.handler.handleToken(expression.toString()))
        } while(start > -1);
        // ...
        return builder.toString();
    }

循环查找#{} 通过 字符串length 方法找到偏移量

GenericTokenParser 对象是通过 SqlSourceBuilder 构造函数生成的

核心部分如下

    public SqlSource parse(/*...*/) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql;
        // 该过程 将#{}替换为 ?
        if (this.configuration.isShrinkWhitespacesInSql()) {
            sql = parser.parse(removeExtraWhitespaces(originalSql));
        } else {
            sql = parser.parse(originalSql);
        }
        return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
    }


// ParameterMappingTokenHandler 类是 `SqlSourceBuilder` 类的一个内部类
// 生成 "?"
//
 private final List<ParameterMapping> parameterMappings = new ArrayList();
 public String handleToken(String content) {
     // 保存 参数 名称 比如 #{username} 替换为 ? ,但是将 username 保存到
     this.parameterMappings.add(this.buildParameterMapping(content));
     return "?";
 }

六、mybaits中的设计模式

  1. 工厂模式

    • mybatis的会话是通过会话工厂创建。 SqlSessionFactoryObjectFactoryMapperProxyFactory
    • 一般情况下如果不指定会话,系统调用的是默认的 SqlSessionFactory
  2. 建造者模式

    • 这个就不多介绍了,常见的 xxxBuilder类都是建造者模式。
  3. 代理模式。

    • mybatis中有 cglibjdk自带的代理工具 Reflect类实现。
    • 关于 代理模式装饰者模式适配器模式 之间区别可百度,这里简单说下
      • 代理模式: 补充功能的同时,更多是为了增加访问控制权限
      • 适配器模式: 更多是为了兼容性,同一个接口方法使用多种功能
      • 装饰器模式:在原来基础功能上添加一些功能。
  4. 适配器模式

    • 日志功能实现
  5. 装饰者模式

    • 例如Cache包中的cache.decorators子包中等各个装饰者的实现
  6. 策略模式

    • 执行 sqlNode, 决定使用哪个 sqlNode 器执行过程
    • 执行 BaseExecutor,mybatis中有个四个类继承了(具体可看上面关系图) BaseExecutor 如果没有指定话,有一个默认的兜底。
  7. 单例模式

    • ErrorContext
    • LogFactory ,PS : 这个不是工厂模式 🤣
  8. 迭代器模式

    • #{} 替换为 ?过程就是用了迭代器模式,具体细节参考 GenericTokenParser
posted @ 2023-03-07 22:26  wuxin001  阅读(19)  评论(0编辑  收藏  举报