手写Mybatis:第9章-细化XML语句构建器,完善静态SQL解析

文章目录

  • 一、目标:XML语句构建器
  • 二、设计:XML语句构建器
  • 三、实现:XML语句构建器
    • [3.0 引入依赖](#3.0 引入依赖)
    • [3.1 工程结构](#3.1 工程结构)
    • [3.2 XML语句构建器关系图](#3.2 XML语句构建器关系图)
    • [3.3 I/O资源扫描](#3.3 I/O资源扫描)
    • [3.4 SQL源码](#3.4 SQL源码)
      • [3.4.1 SQL对象](#3.4.1 SQL对象)
      • [3.4.2 SQL源码接口](#3.4.2 SQL源码接口)
      • [3.4.3 原始SQL源码实现类](#3.4.3 原始SQL源码实现类)
      • [3.4.4 静态SQL源码实现类](#3.4.4 静态SQL源码实现类)
    • [3.5 动态上下文](#3.5 动态上下文)
    • [3.6 SQL节点](#3.6 SQL节点)
      • [3.6.1 SQL节点接口](#3.6.1 SQL节点接口)
      • [3.6.2 混合SQL节点实现类](#3.6.2 混合SQL节点实现类)
      • [3.6.3 静态文本SQL节点](#3.6.3 静态文本SQL节点)
    • [3.7 脚本语言驱动](#3.7 脚本语言驱动)
      • [3.7.1 脚本语言驱动接口](#3.7.1 脚本语言驱动接口)
      • [3.7.2 XML语言驱动器](#3.7.2 XML语言驱动器)
      • [3.7.3 脚本语言注册器](#3.7.3 脚本语言注册器)
    • [3.8 类型处理器](#3.8 类型处理器)
      • [3.8.1 类型处理器接口](#3.8.1 类型处理器接口)
      • [3.8.2 类型处理器注册机](#3.8.2 类型处理器注册机)
    • [3.9 记号处理器](#3.9 记号处理器)
      • [3.9.1 记号处理器接口](#3.9.1 记号处理器接口)
      • [3.9.2 普通记号解析器](#3.9.2 普通记号解析器)
    • [3.10 参数表达式](#3.10 参数表达式)
    • [3.11 修改映射器语句和参数映射](#3.11 修改映射器语句和参数映射)
      • [3.11.1 修改映射器语句](#3.11.1 修改映射器语句)
      • [3.11.2 修改参数映射](#3.11.2 修改参数映射)
    • [3.12 修改类型别名](#3.12 修改类型别名)
    • [3.13 修改配置文件](#3.13 修改配置文件)
    • [3.14 解析构建器](#3.14 解析构建器)
      • [3.14.1 修改构建器基类](#3.14.1 修改构建器基类)
      • [3.14.2 XML脚本构建器](#3.14.2 XML脚本构建器)
      • [3.14.3 SQL源码构建器](#3.14.3 SQL源码构建器)
      • [3.14.4 XML语言构建器](#3.14.4 XML语言构建器)
      • [3.14.5 XML映射构建器,解耦映射解析](#3.14.5 XML映射构建器,解耦映射解析)
      • [3.14.6 XML配置构建器](#3.14.6 XML配置构建器)
    • [3.15 DefaultSqlSession 调用调整](#3.15 DefaultSqlSession 调用调整)
  • 四、测试:XML语句构建器
  • 五、总结:XML语句构建器

一、目标:XML语句构建器

  • Mybatis ORM 框架的核心结构逐步体现出来,包括:解析、绑定、映射、事务、执行、数据源等。
  • 着手处理 XML 解析问题。满足我们解析时一些参数的整合和处理

💡 这部分的解析,就是在 XMLConfigBuilder#mapperElement 方法中的操作。看上去实现了功能,但是会让人感觉不够规整。

怎么才能设计的易于扩展,又比较干净?

  • 将这部分解析的处理,使用设计原则将流程和职责进行解耦,并结合我们的当前诉求,优先处理静态 SQL 内容,后面再处理动态 SQL 和更多的参数类型的处理。

二、设计:XML语句构建器

💡 怎么使用设计原则将流程和职责进行解耦,并处理静态SQL内容?

  • 参照设计原则,对于 XML 信息的读取,各个功能模块的流程上应该符合单一职责,而每个具体的实现又得具备迪米特法则,这样实现出来的功能才能具备良好的扩展性。通常这类代码也会看着很干净
  • 在解析过程中,所属解析的不同内容,按照各自的职责类进行拆解和串联调用。
  • 不把所有的解析都在一个循环中处理,而是在整个解析过程中, 引入 映射构建器语句构建器 ,按照不同的职责分别进行解析。
    • XMLMapperBuilder: 映射构建器
    • XMLStatementBuilder : 语句构建器
      • 同时在语句构建器中,引入脚本语言驱动器 ,默认实现的是 XMLLanguageDriver,这个类来具体操作静态和动态 SQL 语句节点的解析。
      • Mybatis 源码中使用 Ognl 的方式进行处理。对于的类是 DynamicContext

三、实现:XML语句构建器

3.0 引入依赖

pom.xml

xml 复制代码
<!-- https://mvnrepository.com/artifact/ognl/ognl -->
<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.3.2</version>
</dependency>

3.1 工程结构

java 复制代码
mybatis-step-08
|-src
	|-main
	|	|-java
	|		|-com.lino.mybatis
    |			|-binding
    |			|	|-MapperMethod.java
	|			|	|-MapperProxy.java
	|			|	|-MapperProxyFactory.java
    |			|	|-MapperRegistry.java
    |			|-builder
    |			|	|-xml
    |			|	|	|-XMLConfigBuilder.java
    |			|	|	|-XMLMapperBuilder.java
    |			|	|	|-XMLStatementBuilder.java
    |			|	|-BaseBuilder.java
    |			|	|-ParameterExpression.java
    |			|	|-SqlSourceBuilder.java
    |			|	|-StaticSqlSource.java
	|			|-datasource
	|			|	|-druid
	|			|	|	|-DruidDataSourceFacroty.java
	|			|	|-pooled
	|			|	|	|-PooledConnection.java
	|			|	|	|-PooledDataSource.java
	|			|	|	|-PooledDataSourceFacroty.java
	|			|	|	|-PoolState.java
	|			|	|-unpooled
	|			|	|	|-UnpooledDataSource.java
	|			|	|	|-UnpooledDataSourceFacroty.java
	|			|	|-DataSourceFactory.java
	|			|-executor
	|			|	|-resultset
	|			|	|	|-DefaultResultSetHandler.java
	|			|	|	|-ResultSetHandler.java
	|			|	|-statement
	|			|	|	|-BaseStatementHandler.java
	|			|	|	|-PreparedStatementHandler.java
	|			|	|	|-SimpleStatementHandler.java
	|			|	|	|-StatementHandler.java
	|			|	|-BaseExecutor.java
	|			|	|-Executor.java
	|			|	|-SimpleExecutor.java
    |			|-io
    |			|	|-Resources.java
    |			|-mapping
    |			|	|-BoundSql.java
    |			|	|-Environment.java
    |			|	|-MappedStatement.java
    |			|	|-ParameterMapping.java
    |			|	|-SqlCommandType.java
    |			|	|-SqlSource.java
    |			|-parsing
    |			|	|-GenericTokenParser.java
    |			|	|-TokenHandler.java
	|			|-reflection
	|			|	|-factory
	|			|	|	|-DefaultObjectFactory.java
	|			|	|	|-ObjectFactory.java
	|			|	|-invoker
	|			|	|	|-GetFieldInvoker.java
	|			|	|	|-Invoker.java
	|			|	|	|-MethodInvoker.java
	|			|	|	|-SetFieldInvoker.java
	|			|	|-property
	|			|	|	|-PropertyNamer.java
	|			|	|	|-PropertyTokenizer.java
	|			|	|-wrapper
	|			|	|	|-BaseWrapper.java
	|			|	|	|-BeanWrapper.java
	|			|	|	|-CollectionWrapper.java
	|			|	|	|-DefaultObjectWrapperFactory.java
	|			|	|	|-MapWrapper.java
	|			|	|	|-ObjectWrapper.java
	|			|	|	|-ObjectWrapperFactory.java
	|			|	|-MetaClass.java
	|			|	|-MetaObject.java
	|			|	|-Reflector.java
	|			|	|-SystemMetaObject.java
	|			|-scripting
	|			|	|-defaults
	|			|	|	|-RawSqlSource.java
	|			|	|-xmltags
	|			|	|	|-DynamicContext.java
	|			|	|	|-MixedSqlNode.java
	|			|	|	|-SqlNode.java
	|			|	|	|-StaticTextSqlNode.java
	|			|	|	|-XMLLanguageDriver.java
	|			|	|	|-XMLScriptBuilder.java
	|			|	|-LanguageDriver.java
	|			|	|-LanguageDriverRegistry.java
    |			|-session
    |			|	|-defaults
    |			|	|	|-DefaultSqlSession.java
    |			|	|	|-DefaultSqlSessionFactory.java
    |			|	|-Configuration.java
    |			|	|-ResultHandler.java
    |			|	|-SqlSession.java
    |			|	|-SqlSessionFactory.java
    |			|	|-SqlSessionFactoryBuilder.java
    |			|	|-TransactionIsolationLevel.java
    |			|-transaction
    |			|	|-jdbc
    |			|	|	|-JdbcTransaction.java
    |			|	|	|-JdbcTransactionFactory.java
    |			|	|-Transaction.java
    |			|	|-TransactionFactory.java
    |			|-type
    |			|	|-JdbcType.java
    |			|	|-TypeAliasRegistry.java
    |			|	|-TypeHandler.java
    |			|	|-TypeHandlerRegistry.java
	|-test
		|-java
		|	|-com.lino.mybatis.test
		|	|-dao
		|	|	|-IUserDao.java
		|	|-po
		|	|	|-User.java
		|	|-ApiTest.java
        |-resources
        	|-mapper
        	|	|-User_Mapper.xml
        	|-mybatis-config-datasource.xml

3.2 XML语句构建器关系图

  • 解耦原 XMLConfigBuilder 中对 XML 的解析,扩展映射构建器、语句构建器,处理 SQL 的提取和参数的包装,整个核心流程图以 XMLConfigBuilder#mapperElement 为入口串联调用。
  • XMLStatement#parseStatementNode 方法中解析配置语句,提取参数类型、结果类型
    • <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User"> SELECT id, userId, userName, userHead FROM user WHERE id = #{id} </select>
    • 这里使用到脚本语言驱动器,今昔解析处理,创建 SqlSource 语句信息。SqlSource 包含了 BoundSql
    • 同时这里扩展了 ParameterMapping 作为参数包装传递类,而不仅仅作为 Map 结构包装。因为通过这样的方式,可以封装解析后的 javaType/jdbcType 信息

3.3 I/O资源扫描

Resources.java

java 复制代码
package com.lino.mybatis.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

/**
 * @description: 通过类加载器获得resource的辅助类
 */
public class Resources {

    public static Reader getResourceAsReader(String resource) throws IOException {
        return new InputStreamReader(getResourceAsStream(resource));
    }

    public static InputStream getResourceAsStream(String resource) throws IOException {
        ClassLoader[] classLoaders = getClassLoaders();
        for (ClassLoader classLoader : classLoaders) {
            InputStream inputStream = classLoader.getResourceAsStream(resource);
            if (null != inputStream) {
                return inputStream;
            }
        }
        throw new IOException("Could not find resource " + resource);
    }

    private static ClassLoader[] getClassLoaders() {
        return new ClassLoader[]{
                ClassLoader.getSystemClassLoader(),
                Thread.currentThread().getContextClassLoader()};
    }

    /**
     * 获取类示例
     * @param className 类名称
     * @return 实例类
     */
    public static Class<?> classForName(String className) throws ClassNotFoundException {
        return Class.forName(className);
    }
}
  • getResourceAsStreamprivate 改为 public

3.4 SQL源码

3.4.1 SQL对象

BoundSql.java

java 复制代码
package com.lino.mybatis.mapping;

import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: 绑定的SQL,是从SqlSource而来,将动态内容都处理完成得到的SQL语句字符串,其中包括?,还有绑定的参数
 */
public class BoundSql {

    private String sql;
    private List<ParameterMapping> parameterMappings;
    private Object parameterObject;
    private Map<String, Object> additionalParameters;
    private MetaObject metaParameters;

    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }

    public String getSql() {
        return sql;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public Object getParameterObject() {
        return parameterObject;
    }

    public Object getAdditionalParameter(String name) {
        return metaParameters.getValue(name);
    }

    public void setAdditionalParameter(String name, Object value) {
        metaParameters.setValue(name, value);
    }

    public boolean hasAdditionalParameter(String name) {
        return metaParameters.hasGetter(name);
    }
}

3.4.2 SQL源码接口

SqlSource.java

java 复制代码
package com.lino.mybatis.mapping;

/**
 * @description: SQL源码
 */
public interface SqlSource {
    /**
     * 获取SQL源
     *
     * @param parameterObject 参数对象
     * @return BoundSqlSQL源
     */
    BoundSql getBoundSql(Object parameterObject);
}

3.4.3 原始SQL源码实现类

RawSqlSource.java

java 复制代码
package com.lino.mybatis.scripting.defaults;

import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.builder.SqlSourceBuilder;
import com.lino.mybatis.scripting.xmltags.DynamicContext;
import com.lino.mybatis.scripting.xmltags.SqlNode;
import com.lino.mybatis.session.Configuration;
import java.util.HashMap;

/**
 * @description: 原始SQL源码, 比 DynamicSqlSource 动态SQL处理快
 */
public class RawSqlSource implements SqlSource {

    private final SqlSource sqlSource;

    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
        this(configuration, getSql(configuration, rootSqlNode), parameterType);
    }

    public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        return sqlSource.getBoundSql(parameterObject);
    }

    private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
        DynamicContext context = new DynamicContext(configuration, null);
        rootSqlNode.apply(context);
        return context.getSql();
    }
}

3.4.4 静态SQL源码实现类

StaticSqlSource.java

java 复制代码
package com.lino.mybatis.builder;

import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.session.Configuration;
import java.util.List;

/**
 * @description: 静态sql源码
 */
public class StaticSqlSource implements SqlSource {

    private String sql;
    private List<ParameterMapping> parameterMappings;
    private Configuration configuration;

    public StaticSqlSource(Configuration configuration, String sql) {
        this(configuration, sql, null);
    }

    public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.configuration = configuration;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }
}

3.5 动态上下文

DynamicContext.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 动态上下文
 */
public class DynamicContext {

    public static final String PARAMETER_OBJECT_KEY = "_parameter";
    public static final String DATABASE_ID_KEY = "_databaseId";

    static {
        // 定义属性->getter方法映射,ContextMap到ContextAccessor的映射,注册到ognl运行时
        // 参考http://commons.apache.org/proper/commons-ognl/developer-guide.html
        OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
        // 将传入的参数对象统一封装为ContextMap对象(继承了HashMap对象),
        // 然后Ognl运行时环境在动态计算sql语句时,
        // 会按照ContextAccessor中描述的Map接口的方式来访问和读取ContextMap对象,获取计算过程中需要的参数。
        // ContextMap对象内部可能封装了一个普通的POJO对象,也可以是直接传递的Map对象,当然从外部是看不出来的,因为都是使用Map的接口来读取数据。
    }

    private final ContextMap bindings;
    private final StringBuilder sqlBuilder = new StringBuilder();
    private int uniqueNumber;

    /**
     * 在DynamicContext的构造函数中,根据传入的参数对象是否为Map类型,有两个不同构造ContextMap的方式。
     * 而ContextMap作为一个继承了HashMap的对象,作用就是用于统一参数的访问方式;用Map接口方法来访问数据。
     * 具体来说,当传入的参数对象不是Map类型时,Mybatis会将传入的POJO对象用MetaObject对象来封装。
     * 当动态计算sql过程需要获取数据时,用Map接口的get方法包装 MetaObject对象的取值过程。
     *
     * @param configuration   配置项
     * @param parameterObject 参数对象
     */
    public DynamicContext(Configuration configuration, Object parameterObject) {
        // 绝大多数调用的地方 parameterObject 为null
        if (parameterObject != null && !(parameterObject instanceof Map)) {
            // 如果是map型 ?? 这句是 如果不是map型
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            bindings = new ContextMap(metaObject);
        } else {
            bindings = new ContextMap(null);
        }
        bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
        bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
    }

    public Map<String, Object> getBindings() {
        return bindings;
    }

    public void bind(String name, Object value) {
        bindings.put(name, value);
    }

    public void appendSql(String sql) {
        sqlBuilder.append(sql);
        sqlBuilder.append(" ");
    }

    public String getSql() {
        return sqlBuilder.toString().trim();
    }

    public int getUniqueNumber() {
        return uniqueNumber++;
    }

    /**
     * 上下文map, 静态内部类
     */
    static class ContextMap extends HashMap<String, Object> {

        private static final long serialVersionUID = 2977601501966151582L;

        private MetaObject parameterMetaObject;

        public ContextMap(MetaObject parameterMetaObject) {
            this.parameterMetaObject = parameterMetaObject;
        }

        @Override
        public Object get(Object key) {
            String strKey = (String) key;
            // 先去map中找
            if (super.containsKey(strKey)) {
                return super.get(strKey);
            }
            // 如果没找到,再用ognl表达式去取值
            // 如person[0].birthdate.year
            if (parameterMetaObject != null) {
                return parameterMetaObject.getValue(strKey);
            }
            return null;
        }
    }

    static class ContextAccessor implements PropertyAccessor {

        @Override
        public Object getProperty(Map context, Object target, Object name) throws OgnlException {
            Map map = (Map) target;

            Object result = map.get(name);
            if (result != null) {
                return result;
            }

            Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
            if (parameterObject instanceof Map) {
                return ((Map) parameterObject).get(name);
            }
            return null;
        }

        @Override
        public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
            Map<Object, Object> map = (Map<Object, Object>) target;
            map.put(name, value);
        }

        @Override
        public String getSourceAccessor(OgnlContext ognlContext, Object o, Object o1) {
            return null;
        }

        @Override
        public String getSourceSetter(OgnlContext ognlContext, Object o, Object o1) {
            return null;
        }
    }
}

3.6 SQL节点

3.6.1 SQL节点接口

SqlNode.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

/**
 * @description: SQL 节点
 */
public interface SqlNode {
    /**
     * 应用动态上下文
     *
     * @param context 动态上下文
     * @return boolean
     */
    boolean apply(DynamicContext context);
}

3.6.2 混合SQL节点实现类

MixedSqlNode.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

import java.util.List;

/**
 * @description: 混合SQL节点
 */
public class MixedSqlNode implements SqlNode {

    /**
     * 组合模式,拥有一个SqlNode的List
     */
    private List<SqlNode> contexts;

    public MixedSqlNode(List<SqlNode> contexts) {
        this.contexts = contexts;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 依次调用list里每个元素的apply
        contexts.forEach(node -> node.apply(context));
        return true;
    }
}

3.6.3 静态文本SQL节点

StaticTestNode.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

/**
 * @description: 静态文本SQL节点
 */
public class StaticTextSqlNode implements SqlNode {

    private String text;

    public StaticTextSqlNode(String text) {
        this.text = text;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 将文本加入context中
        context.appendSql(text);
        return true;
    }
}

3.7 脚本语言驱动

  • 获取默认语言驱动器并解析 SQL 操作。在 XMLSriptBuilder 中处理静态 SQL 和 动态 SQL

3.7.1 脚本语言驱动接口

LanguageDriver.java

java 复制代码
package com.lino.mybatis.scripting;

import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;

/**
 * @description: 脚本语言驱动
 */
public interface LanguageDriver {

    /**
     * 创建SQL源
     *
     * @param configuration 配置项
     * @param script        元素
     * @param parameterType 参数类型
     * @return SqlSource SQL源码
     */
    SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType);
}
  • 定义脚本语言驱动接口,提供创建 SQL 信息的方法,入参包括:配置、元素、参数。
  • 它有3个实现类:XMLLanguageDriverRawLanguageDriverVelocityLanguageDriver,这里只实现了第一种实现。

3.7.2 XML语言驱动器

XMLLanguageDriver.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;

/**
 * @description: XML 语言驱动器
 */
public class XMLLanguageDriver implements LanguageDriver {

    @Override
    public SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {
        // 用XML脚本构建器解析
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }
}
  • 关于 XML 语言驱动器的实现较简单,只是封装了对 XMLScriptBuilder 的调用处理。

3.7.3 脚本语言注册器

LanguageDriverRegistry.java

java 复制代码
package com.lino.mybatis.scripting;

import java.util.HashMap;
import java.util.Map;

/**
 * @description: 脚本语言注册器
 */
public class LanguageDriverRegistry {
    /**
     * 脚本语言Map
     */
    private final Map<Class<?>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>(16);

    private Class<?> defaultDriverClass = null;

    public void register(Class<?> cls) {
        if (cls == null) {
            throw new IllegalArgumentException("null is not a valid Language Driver");
        }
        if (!LanguageDriver.class.isAssignableFrom(cls)) {
            throw new RuntimeException(cls.getName() + " does not implements" + LanguageDriver.class.getName());
        }
        // 如果没注册过,再去注册
        LanguageDriver driver = LANGUAGE_DRIVER_MAP.get(cls);
        if (driver == null) {
            try {
                // 单例模式,即一个Class只有一个对应的LanguageDriver
                driver = (LanguageDriver) cls.newInstance();
                LANGUAGE_DRIVER_MAP.put(cls, driver);
            } catch (Exception e) {
                throw new RuntimeException("Failed to load language driver for " + cls.getName(), e);
            }
        }
    }

    public LanguageDriver getDriver(Class<?> cls) {
        return LANGUAGE_DRIVER_MAP.get(cls);
    }

    public LanguageDriver getDefaultDriver() {
        return getDriver(getDefaultDriverClass());
    }

    public Class<?> getDefaultDriverClass() {
        return defaultDriverClass;
    }

    /**
     * Configuration()有调用, 默认为XMLLanguageDriver
     *
     * @param defaultDriverClass 默认驱动类型
     */
    public void setDefaultDriverClass(Class<?> defaultDriverClass) {
        register(defaultDriverClass);
        this.defaultDriverClass = defaultDriverClass;
    }
}

3.8 类型处理器

3.8.1 类型处理器接口

TypeHandler.java

java 复制代码
package com.lino.mybatis.type;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @description: 类型处理器
 */
public interface TypeHandler<T> {

    /**
     * 设置参数
     *
     * @param ps        预处理语言
     * @param i         次数
     * @param parameter 参数对象
     * @param jdbcType  JDBC类型
     * @throws SQLException SQL异常
     */
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}

3.8.2 类型处理器注册机

TypeHandlerRegistry.java

java 复制代码
package com.lino.mybatis.type;

import java.lang.reflect.Type;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 类型处理器注册机
 */
public final class TypeHandlerRegistry {

    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>(16);
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLER_MAP = new HashMap<>(16);

    public TypeHandlerRegistry() {
    }

    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (null != javaType) {
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.computeIfAbsent(javaType, k -> new HashMap<>(16));
            map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLER_MAP.put(handler.getClass(), handler);
    }
}

3.9 记号处理器

3.9.1 记号处理器接口

TokenHandler.java

java 复制代码
package com.lino.mybatis.parsing;

/**
 * @description: 记号处理器
 */
public interface TokenHandler {
    /**
     * 处理记号
     *
     * @param content 内容
     * @return String 处理结果
     */
    String handleToken(String content);
}

3.9.2 普通记号解析器

GenericTokenParser

java 复制代码
package com.lino.mybatis.parsing;

/**
 * @description: 普通记号解析器,处理 #{} 和 ${} 参数
 */
public class GenericTokenParser {
    /**
     * 开始记号
     */
    private final String openToken;
    /**
     * 结束记号
     */
    private final String closeToken;
    /**
     * 记号处理器
     */
    private final TokenHandler handler;

    public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
    }

    public String parse(String text) {
        StringBuilder builder = new StringBuilder();
        if (text != null && text.length() > 0) {
            char[] src = text.toCharArray();
            int offset = 0;
            int start = text.indexOf(openToken, offset);
            // #{favouriteSection,jdbcType=VARCHAR}
            // 这里循环解析参数,参考 GenericTokenParserTest, 如果解析 ${first_name} ${initial} ${last_name} reporting.这样的字符串,里面有3个${}
            while (start > -1) {
                // 判断一下 ${ 前面是否有反斜杠,这个逻辑在老版的mybatis(3.1.0)中是没有的
                if (start > 0 && src[start - 1] == '\\') {
                    // 新版已经没有调用substring了,改为调用offset方式,提高效率
                    builder.append(src, offset, start - offset - 1).append(openToken);
                    offset = start + openToken.length();
                } else {
                    int end = text.indexOf(closeToken, start);
                    if (end == -1) {
                        builder.append(src, offset,src.length - offset);
                        offset = src.length;
                    } else {
                        builder.append(src, offset, start - offset);
                        offset = start + openToken.length();
                        String context = new String(src, offset, end - offset);
                        // 得到一对大括号里的字符串后,调用handler.handleToken,比如替换变量
                        builder.append(handler.handleToken(context));
                        offset = end + closeToken.length();
                    }
                }
                start = text.indexOf(openToken, offset);
            }
            if (offset < src.length) {
                builder.append(src, offset, src.length - offset);
            }
        }
        return builder.toString();
    }
}

3.10 参数表达式

ParameterExpression.java

java 复制代码
package com.lino.mybatis.builder;

import java.util.HashMap;

/**
 * @description: 参数表达式
 */
public class ParameterExpression extends HashMap<String, String> {

    private static final long serialVersionUID = -2417552199605158680L;

    public ParameterExpression(String expression) {
        parse(expression);
    }

    private void parse(String expression) {
        // #{property,javaType=int,jdbcType=NUMERIC}
        // 首先去除空白,返回的p是第一个不是空白的字符位置
        int p = skipWS(expression, 0);
        if (expression.charAt(p) == '(') {
            // 处理表达式
            expression(expression, p + 1);
        } else {
            // 处理属性
            property(expression, p);
        }
    }

    /**
     * 表达式可能是3.2的新功能
     *
     * @param expression 参数
     * @param left       索引
     */
    private void expression(String expression, int left) {
        int match = 1;
        int right = left + 1;
        while (match > 0) {
            if (expression.charAt(right) == ')') {
                match--;
            } else if (expression.charAt(right) == '(') {
                match++;
            }
            right++;
        }
        put("expression", expression.substring(left, right - 1));
        jdbcTypeOpt(expression, right);
    }

    private void property(String expression, int left) {
        // #{property,javaType=int,jdbcType=NUMERIC}
        // property:VARCHAR
        if (left < expression.length()) {
            // 首先,得到逗号或者冒号之前的字符串,加入到property
            int right = skipUntil(expression, left, ",:");
            put("property", trimmedStr(expression, left, right));
            // 第二,处理javaType,jdbcType
            jdbcTypeOpt(expression, right);
        }
    }

    private int skipWS(String expression, int p) {
        for (int i = p; i < expression.length(); i++) {
            if (expression.charAt(i) > 0x20) {
                return i;
            }
        }
        return expression.length();
    }

    private int skipUntil(String expression, int p, String endChars) {
        for (int i = p; i < expression.length(); i++) {
            char c = expression.charAt(i);
            if (endChars.indexOf(c) > -1) {
                return i;
            }
        }
        return expression.length();
    }

    private void jdbcTypeOpt(String expression, int p) {
        // #{property,javaType=int,jdbcType=NUMERIC}
        // property:VARCHAR
        // 首先去除空白,返回的p是第一个不是空白的字符位置
        p = skipWS(expression, p);
        if (p < expression.length()) {
            // 第一个property解析完有两种情况,逗号和冒号
            if (expression.charAt(p) == ':') {
                jdbcType(expression, p + 1);
            } else if (expression.charAt(p) == ',') {
                option(expression, p + 1);
            } else {
                throw new RuntimeException("Parsing error in {" + new String(expression) + "} in position " + p);
            }
        }
    }

    private void jdbcType(String expression, int p) {
        // property:VARCHAR
        int left = skipWS(expression, p);
        int right = skipUntil(expression, left, ",");
        if (right > left) {
            put("jdbcType", trimmedStr(expression, left, right));
        } else {
            throw new RuntimeException("Parsing error in {" + new String(expression) + "} in position " + p);
        }
        option(expression, right + 1);
    }

    private void option(String expression, int p) {
        // #{property,javaType=int,jdbcType=NUMERIC}
        int left = skipWS(expression, p);
        if (left < expression.length()) {
            int right = skipUntil(expression, left, "=");
            String name = trimmedStr(expression, left, right);
            left = right + 1;
            right = skipUntil(expression, left, ",");
            String value = trimmedStr(expression, left, right);
            put(name, value);
            // 递归调用option,进行逗号后面一个属性的解析
            option(expression, right + 1);
        }
    }

    private String trimmedStr(String str, int start, int end) {
        while (str.charAt(start) <= 0x20) {
            start++;
        }
        while (str.charAt(end - 1) <= 0x20) {
            end--;
        }
        return start >= end ? "" : str.substring(start, end);
    }
}

3.11 修改映射器语句和参数映射

3.11.1 修改映射器语句

MappedStatement.java

java 复制代码
package com.lino.mybatis.mapping;

import com.lino.mybatis.session.Configuration;

/**
 * @description: 映射器语句类
 */
public class MappedStatement {

    private Configuration configuration;
    private String id;
    private SqlCommandType sqlCommandType;
    private SqlSource sqlSource;
    Class<?> resultType;

    public MappedStatement() {
    }

    public static class Builder {

        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlCommandType = sqlCommandType;
            mappedStatement.sqlSource = sqlSource;
            mappedStatement.resultType = resultType;
        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            return mappedStatement;
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public SqlSource getSqlSource() {
        return sqlSource;
    }

    public Class<?> getResultType() {
        return resultType;
    }
}
  • 修改 BoundSqlSqlSource

3.11.2 修改参数映射

ParameterMapping.java

java 复制代码
package com.lino.mybatis.mapping;

import cn.hutool.db.meta.JdbcType;
import com.lino.mybatis.session.Configuration;

/**
 * @description: 参数映射 #{property,javaType=int,jdbcType=NUMERIC}
 */
public class ParameterMapping {

    private Configuration configuration;
    /**
     * property
     */
    private String property;
    /**
     * javaType = int
     */
    private Class<?> javaType = Object.class;
    /**
     * javaType = NUMERIC
     */
    private JdbcType jdbcType;

    private ParameterMapping() {
    }

    public static class Builder {

        private ParameterMapping parameterMapping = new ParameterMapping();

        public Builder(Configuration configuration, String property, Class<?> javaType) {
            parameterMapping.configuration = configuration;
            parameterMapping.property = property;
            parameterMapping.javaType = javaType;
        }

        public Builder javaType(Class<?> javaType) {
            parameterMapping.javaType = javaType;
            return this;
        }

        public Builder jdbcType(JdbcType jdbcType) {
            parameterMapping.jdbcType = jdbcType;
            return this;
        }

        public ParameterMapping build() {
            return parameterMapping;
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getProperty() {
        return property;
    }

    public Class<?> getJavaType() {
        return javaType;
    }

    public JdbcType getJdbcType() {
        return jdbcType;
    }
}

3.12 修改类型别名

TypeAliasRegistry.java

java 复制代码
package com.lino.mybatis.type;

import com.lino.mybatis.io.Resources;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * @description: 类型别名注册机
 */
public class TypeAliasRegistry {

    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<>();

    public TypeAliasRegistry() {
        // 构造函数里注册系统内置的类型别名
        registerAlias("string", String.class);

        // 基本包装类型
        registerAlias("byte", Byte.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);
    }

    public void registerAlias(String alias, Class<?> value) {
        String key = alias.toLowerCase(Locale.ENGLISH);
        TYPE_ALIASES.put(key, value);
    }

    public <T> Class<T> resolveAlias(String string) {
        try {
            if (string == null) {
                return null;
            }
            String key = string.toLowerCase(Locale.ENGLISH);
            Class<T> value;
            if (TYPE_ALIASES.containsKey(key)) {
                value = (Class<T>) TYPE_ALIASES.get(key);
            } else {
                value = (Class<T>) Resources.classForName(string);
            }
            return value;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
        }
    }
}
  • 修改 resolveAlias 方法,添加不同别名之间和异常信息的处理。

3.13 修改配置文件

Configuration.java

java 复制代码
package com.lino.mybatis.session;

import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @description: 配置项
 */
public class Configuration {

    /**
     * 环境
     */
    protected Environment environment;
    /**
     * 映射注册机
     */
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    /**
     * 映射的语句,存在Map里
     */
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
    /**
     * 类型别名注册机
     */
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    /**
     * 脚本语言注册器
     */
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    /**
     * 类型处理器注册机
     */
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    /**
     * 对象工厂
     */
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    /**
     * 对象包装工厂
     */
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    /**
     * 准备资源列表
     */
    protected final Set<String> loadedResources = new HashSet<>();
    /**
     * 数据库ID
     */
    protected String databaseId;

    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

        typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);

        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    }

    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }

    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }

    public boolean hasMapper(Class<?> type) {
        return mapperRegistry.hasMapper(type);
    }

    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }

    public MappedStatement getMappedStatement(String id) {
        return mappedStatements.get(id);
    }

    public TypeAliasRegistry getTypeAliasRegistry() {
        return typeAliasRegistry;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public String getDatabaseId() {
        return databaseId;
    }

    /**
     * 生产执行器
     *
     * @param transaction 事务
     * @return 执行器
     */
    public Executor newExecutor(Transaction transaction) {
        return new SimpleExecutor(this, transaction);
    }

    /**
     * 创建语句处理器
     *
     * @param executor        执行器
     * @param mappedStatement 映射器语句类
     * @param parameter       参数
     * @param resultHandler   结果处理器
     * @param boundSql        SQL语句
     * @return StatementHandler 语句处理器
     */
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
        return new PreparedStatementHandler(executor, mappedStatement, parameter, resultHandler, boundSql);
    }

    /**
     * 创建结果集处理器
     *
     * @param executor        执行器
     * @param mappedStatement 映射器语句类
     * @param boundSql        SQL语句
     * @return ResultSetHandler 结果集处理器
     */
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, BoundSql boundSql) {
        return new DefaultResultSetHandler(executor, mappedStatement, boundSql);
    }

    /**
     * 创建元对象
     *
     * @param object 原对象
     * @return 元对象
     */
    public MetaObject newMetaObject(Object object) {
        return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
    }

    /**
     * 创建类型处理器注册机
     *
     * @return TypeHandlerRegistry 类型处理器注册机
     */
    public TypeHandlerRegistry getTypeHandlerRegistry() {
        return typeHandlerRegistry;
    }

    /**
     * 是否包含资源
     *
     * @param resource 资源
     * @return 是否
     */
    public boolean isResourceLoaded(String resource) {
        return loadedResources.contains(resource);
    }

    /**
     * 添加资源
     *
     * @param resource 资源
     */
    public void addLoadedResource(String resource) {
        loadedResources.add(resource);
    }

    /**
     * 获取脚本语言注册机
     *
     * @return languageRegistry 脚本语言注册机
     */
    public LanguageDriverRegistry getLanguageRegistry() {
        return languageRegistry;
    }
}

3.14 解析构建器

3.14.1 修改构建器基类

BaseBuilder.java

java 复制代码
package com.lino.mybatis.builder;

import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;

/**
 * @description: 构建器的基类,建造者模式
 */
public class BaseBuilder {

    protected final Configuration configuration;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final TypeHandlerRegistry typeHandlerRegistry;

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }
}
  • 添加类型处理器注册机 TypeHandlerRegistry

3.14.2 XML脚本构建器

XMLScriptBuilder.java

java 复制代码
package com.lino.mybatis.scripting.xmltags;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.defaults.RawSqlSource;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: XML脚本构建器
 */
public class XMLScriptBuilder extends BaseBuilder {

    private Element element;
    private boolean isDynamic;
    private Class<?> parameterType;

    public XMLScriptBuilder(Configuration configuration, Element element, Class<?> parameterType) {
        super(configuration);
        this.element = element;
        this.parameterType = parameterType;
    }

    public SqlSource parseScriptNode() {
        List<SqlNode> contents = parseDynamicTags(element);
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        return new RawSqlSource(configuration, rootSqlNode, parameterType);
    }

    private List<SqlNode> parseDynamicTags(Element element) {
        List<SqlNode> contents = new ArrayList<>();
        // element.getText 拿到 SQL
        String data = element.getText();
        contents.add(new StaticTextSqlNode(data));
        return contents;
    }
}
  • XMLScriptBuilder#parseScriptNode 解析 SQL 节点的处理方式,主要是对 RawSqlSource 的包装处理。

3.14.3 SQL源码构建器

SqlSourceBuilder.java

java 复制代码
package com.lino.mybatis.builder;

import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.parsing.GenericTokenParser;
import com.lino.mybatis.parsing.TokenHandler;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @description: SQL源码构建器
 */
public class SqlSourceBuilder extends BaseBuilder {

    private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

    public SqlSourceBuilder(Configuration configuration) {
        super(configuration);
    }

    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        // 返回静态SQL
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        private List<ParameterMapping> parameterMappings = new ArrayList<>();
        private Class<?> parameterType;
        private MetaObject metaParameters;

        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        }

        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        }

        @Override
        public String handleToken(String content) {
            parameterMappings.add(buildParameterMapping(content));
            return "?";
        }

        /**
         * 构建参数映射
         *
         * @param content 参数
         * @return 参数映射
         */
        private ParameterMapping buildParameterMapping(String content) {
            // 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}
            Map<String, String> propertiesMap = new ParameterExpression(content);
            String property = propertiesMap.get("property");
            Class<?> propertyType = parameterType;
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
            return builder.build();
        }
    }
}
  • 在上一章中,关于 BoundSql.parameterMappings 的参数就是来自于 ParameterMappingTokenHandler#buildParameterMapping 方法进行构建处理的。
  • 具体的 javaTypejdbcType 会体现到 ParameterExpression 参数表达式中完成解析操作。

3.14.4 XML语言构建器

  • XMLStatementBuilder 语句构建器主要解析 XMLselect|insert|update|delete 中的语句。

XMLStatementBuilder.java

java 复制代码
package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.Locale;

/**
 * @description: XML语言构建器
 */
public class XMLStatementBuilder extends BaseBuilder {

    private String currentNamespace;
    private Element element;

    public XMLStatementBuilder(Configuration configuration, Element element, String currentNamespace) {
        super(configuration);
        this.element = element;
        this.currentNamespace = currentNamespace;
    }

    /**
     * 解析语句(select|insert|update|delete)
     * <select
     * id="selectPerson"
     * parameterType="int"
     * parameterMap="deprecated"
     * resultType="hashmap"
     * resultMap="personResultMap"
     * flushCache="false"
     * useCache="true"
     * timeout="10000"
     * fetchSize="256"
     * statementType="PREPARED"
     * resultSetType="FORWARD_ONLY">
     * SELECT * FROM PERSON WHERE ID = #{id}
     * </select>
     */
    public void parseStatementNode() {
        String id = element.attributeValue("id");
        // 参数类型
        String parameterType = element.attributeValue("parameterType");
        Class<?> parameterTypeClass = resolveAlias(parameterType);
        // 结果类型
        String resultType = element.attributeValue("resultType");
        Class<?> resultTypeClass = resolveAlias(resultType);
        // 获取命令类型(select|insert|update|delete)
        String nodeName = element.getName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

        // 获取默认语言驱动器
        Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);

        SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);

        MappedStatement mappedStatement =
                new MappedStatement.Builder(configuration, currentNamespace + "." + id, sqlCommandType, sqlSource, resultTypeClass).build();

        // 添加解析 SQL
        configuration.addMappedStatement(mappedStatement);
    }
}
  • 这部分内容的解析,就是从 XMLConfigBuilder 拆解出来关于 Mapper 语句解析的部分,通过这样的解耦设计,会让整个流程更加清晰。
  • XMLStatementBuilder#parseStatementNode 方法是解析 SQL 语句节点的过程,包括:语句的ID、参数类型、结果类型、命令(select|insert|update|delete),以及使用语言驱动器处理和封装 SQL 信息。
  • 当解析完成后写入到 Confiuration 配置文件中的 Map<String, MappedStatement> 映射语句存放中。

3.14.5 XML映射构建器,解耦映射解析

XMLMapperBuilder.java

java 复制代码
package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;

/**
 * @description: XML映射构建器
 */
public class XMLMapperBuilder extends BaseBuilder {

    private Element element;
    private String resource;
    private String currentNamespace;

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) throws DocumentException {
        this(new SAXReader().read(inputStream), configuration, resource);
    }

    public XMLMapperBuilder(Document document, Configuration configuration, String resource) {
        super(configuration);
        this.element = document.getRootElement();
        this.resource = resource;
    }

    /**
     * 解析
     *
     * @throws Exception 异常
     */
    public void parse() throws Exception {
        // 如果当前资源没有加载过再加载,防止重复加载
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(element);
            // 标记一下,已经加载过了
            configuration.addLoadedResource(resource);
            // 绑定映射器到namespace
            configuration.addMapper(Resources.classForName(currentNamespace));
        }
    }

    /**
     * 配置mapper元素
     * <mapper namespace="org.mybatis.example.BlogMapper">
     * <select id="selectBlog" parameterType="int" resultType="Blog">
     * select * from Blog where id = #{id}
     * </select>
     * </mapper>
     *
     * @param element 元素
     */
    private void configurationElement(Element element) {
        // 1.配置namespace
        currentNamespace = element.attributeValue("namespace");
        if ("".equals(currentNamespace)) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }

        // 2.配置select|insert|update|delete
        buildStatementFromContext(element.elements("select"));
    }

    /**
     * 配置select|insert|update|delete
     *
     * @param list 元素列表
     */
    private void buildStatementFromContext(List<Element> list) {
        for (Element element : list) {
            final XMLStatementBuilder statementBuilder = new XMLStatementBuilder(configuration, element, currentNamespace);
            statementBuilder.parseStatementNode();
        }
    }
}
  • XMLMapperBuilder#parse 的解析中,主要体现在资源解析判断、Mapper 解析和绑定映射器。
    • configuration.isResourceLoaded 资源判断避免重复解析,做了一个记录。
    • configuration.addMapper 绑定映射器:主要是把 namespace com.lino.mybatis.test.dao.IUserDao 绑定到 Mapper 上。
      • 也就是注册到映射器注册机里。
    • configurationElement 方法调用的 buildStatementFromContext,重在处理 XML 语句构建器。

3.14.6 XML配置构建器

XMLConfigBuilder.java

java 复制代码
package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * @description: XML配置构建器,建造者模式,集成BaseBuilder
 */
public class XMLConfigBuilder extends BaseBuilder {

    private Element root;

    public XMLConfigBuilder(Reader reader) {
        // 1.调用父类初始化Configuration
        super(new Configuration());
        // 2.dom4j 处理xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            root = document.getRootElement();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     *
     * @return Configuration
     */
    public Configuration parse() {
        try {
            // 环境
            environmentsElement(root.element("environments"));
            // 解析映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
        return configuration;
    }

    /**
     * <environments default="development">
     * <environment id="development">
     * <transactionManager type="JDBC">
     * <property name="..." value="..."/>
     * </transactionManager>
     * <dataSource type="POOLED">
     * <property name="driver" value="${driver}"/>
     * <property name="url" value="${url}"/>
     * <property name="username" value="${username}"/>
     * <property name="password" value="${password}"/>
     * </dataSource>
     * </environment>
     * </environments>
     */
    private void environmentsElement(Element context) throws Exception {
        String environment = context.attributeValue("default");

        List<Element> environmentList = context.elements("environment");
        for (Element e : environmentList) {
            String id = e.attributeValue("id");
            if (environment.equals(id)) {
                // 事务管理器
                TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();

                // 数据源
                Element dataSourceElement = e.element("dataSource");
                DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
                List<Element> propertyList = dataSourceElement.elements("property");
                Properties props = new Properties();
                for (Element property : propertyList) {
                    props.setProperty(property.attributeValue("name"), property.attributeValue("value"));
                }
                dataSourceFactory.setProperties(props);
                DataSource dataSource = dataSourceFactory.getDataSource();

                // 构建环境
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);

                configuration.setEnvironment(environmentBuilder.build());
            }

        }
    }

    /**
     * <mappers>
     * <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
     * <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
     * <mapper resource="org/mybatis/builder/PostMapper.xml"/>
     * </mappers>
     */
    private void mapperElement(Element mappers) throws Exception {
        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            String resource = e.attributeValue("resource");
            InputStream inputStream = Resources.getResourceAsStream(resource);

            // 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
            mapperParser.parse();
        }

    }
}
  • XMLConfigBuilder#mapperElement 中,把原来的流程化的处理进行解耦, 调用 XMLMapperBuilder#parse 方法进行解析处理。

3.15 DefaultSqlSession 调用调整

DefaultSqlSession.java

java 复制代码
package com.lino.mybatis.session.defaults;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.util.List;

/**
 * @description: 默认sqlSession实现类
 */
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T selectOne(String statement) {
        return this.selectOne(statement, null);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        MappedStatement ms = configuration.getMappedStatement(statement);
        List<T> list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));
        return list.get(0);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }
}
  • 这里调整不大,主要体现在获取 SQL 的操作上。ms.getSqlSource().getBoundSql(parameter)

四、测试:XML语句构建器

ApiText.java

java 复制代码
@Test
public void test_SqlSessionFactoryExecutor() throws IOException {
    // 1.从SqlSessionFactory中获取SqlSession
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 2.获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);

    // 3.测试验证
    User user = userDao.queryUserInfoById(1L);
    logger.info("测试结果:{}", JSON.toJSONString(user));
}

测试结果

java 复制代码
10:31:42.114 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:31:43.207 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 331418503.
10:31:43.333 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
  • 从测试结果和调试的截图可以看出,我们的 XML 解析处理拆解后,已经顺利支撑我们的使用。

五、总结:XML语句构建器

  • 将原来的 CRUD 的代理,通过设计原则进行拆分和解耦,运用不用的类来承担不同的职责,完成整个功能的实现。
  • 包括:映射构建器、语句构建器、源码构建器的综合使用,以及对应的引用,脚本语言驱动和脚本构建器解析,处理我们的 XML 中的 SQL 语句。
相关推荐
鸠。9 分钟前
Java基础复习(JavaSE进阶)第八章 多线程
java·开发语言
AEMC马广川13 分钟前
关于综合能源服务认证证书的全解析专业认证团队
java·大数据·服务器·能源
菜就多练吧1 小时前
JVM 内存分布详解
java·开发语言·jvm
0白露2 小时前
设计模式之工厂方法模式
java·python·设计模式·php·工厂方法模式
triticale2 小时前
【数论】快速幂
java·算法
爱的叹息3 小时前
【java实现+4种变体完整例子】排序算法中【计数排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·算法·排序算法
橘猫云计算机设计6 小时前
基于Springboot的自习室预约系统的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·毕业设计
秋书一叶6 小时前
SpringBoot项目打包为window安装包
java·spring boot·后端
碎梦归途6 小时前
23种设计模式-结构型模式之外观模式(Java版本)
java·开发语言·jvm·设计模式·intellij-idea·外观模式