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

文章目录

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

<!-- https://mvnrepository.com/artifact/ognl/ognl -->
<dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.3.2</version>
</dependency>

3.1 工程结构

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

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

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

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

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<>());}@Overridepublic 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

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;}@Overridepublic BoundSql getBoundSql(Object parameterObject) {return new BoundSql(configuration, sql, parameterMappings, parameterObject);}
}

3.5 动态上下文

DynamicContext.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.htmlOgnlRuntime.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 为nullif (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;}@Overridepublic Object get(Object key) {String strKey = (String) key;// 先去map中找if (super.containsKey(strKey)) {return super.get(strKey);}// 如果没找到,再用ognl表达式去取值// 如person[0].birthdate.yearif (parameterMetaObject != null) {return parameterMetaObject.getValue(strKey);}return null;}}static class ContextAccessor implements PropertyAccessor {@Overridepublic 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;}@Overridepublic void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {Map<Object, Object> map = (Map<Object, Object>) target;map.put(name, value);}@Overridepublic String getSourceAccessor(OgnlContext ognlContext, Object o, Object o1) {return null;}@Overridepublic String getSourceSetter(OgnlContext ognlContext, Object o, Object o1) {return null;}}
}

3.6 SQL节点

3.6.1 SQL节点接口

SqlNode.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

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;}@Overridepublic boolean apply(DynamicContext context) {// 依次调用list里每个元素的applycontexts.forEach(node -> node.apply(context));return true;}
}

3.6.3 静态文本SQL节点

StaticTestNode.java

package com.lino.mybatis.scripting.xmltags;/*** @description: 静态文本SQL节点*/
public class StaticTextSqlNode implements SqlNode {private String text;public StaticTextSqlNode(String text) {this.text = text;}@Overridepublic boolean apply(DynamicContext context) {// 将文本加入context中context.appendSql(text);return true;}
}

3.7 脚本语言驱动

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

3.7.1 脚本语言驱动接口

LanguageDriver.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

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 {@Overridepublic 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

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只有一个对应的LanguageDriverdriver = (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

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

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

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

3.9.2 普通记号解析器

GenericTokenParser

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

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:VARCHARif (left < expression.length()) {// 首先,得到逗号或者冒号之前的字符串,加入到propertyint right = skipUntil(expression, left, ",:");put("property", trimmedStr(expression, left, right));// 第二,处理javaType,jdbcTypejdbcTypeOpt(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:VARCHARint 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

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

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

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

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

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

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 拿到 SQLString data = element.getText();contents.add(new StaticTextSqlNode(data));return contents;}
}
  • XMLScriptBuilder#parseScriptNode 解析 SQL 节点的处理方式,主要是对 RawSqlSource 的包装处理。

3.14.3 SQL源码构建器

SqlSourceBuilder.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);// 返回静态SQLreturn 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;}@Overridepublic 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

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

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

XMLMapperBuilder.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);// 绑定映射器到namespaceconfiguration.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.配置namespacecurrentNamespace = element.attributeValue("namespace");if ("".equals(currentNamespace)) {throw new RuntimeException("Mapper's namespace cannot be empty");}// 2.配置select|insert|update|deletebuildStatementFromContext(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

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.调用父类初始化Configurationsuper(new Configuration());// 2.dom4j 处理xmlSAXReader 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

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;}@Overridepublic <T> T selectOne(String statement) {return this.selectOne(statement, null);}@Overridepublic <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);}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}@Overridepublic Configuration getConfiguration() {return configuration;}
}
  • 这里调整不大,主要体现在获取 SQL 的操作上。ms.getSqlSource().getBoundSql(parameter)

四、测试:XML语句构建器

ApiText.java

@Test
public void test_SqlSessionFactoryExecutor() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionSqlSessionFactory 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));
}

测试结果

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 语句。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/67838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C到C++的升级

C和C的关系 C继承了所有C语言的特性&#xff1b;C在C的基础上提供了更多的语法和特性&#xff0c;C语言去除了一些C语言的不好的特性。C的设计目标是运行效率与开发效率的统一。 变化一&#xff1a;所有变量都可以在使用时定义 C中更强调语言的实用性&#xff0c;所有的变量…

CentOS7 Hadoop3.3.0 安装与配置

一、安装JDK 1、创建文件夹tools和training用于存放压缩包和解压使用&#xff0c;tools存放压缩包&#xff0c;training用于解压后安装jdk和hadoop的路径。 1&#xff09;回到路径为 / 的位置 cd /2) 创建 tools 和 training mkdir toolsmkdir training3) 进入tools文件夹 …

测试工程师的领航指南:《Effective软件测试》

目录 前言一、本书适合对象二、本书大纲第1章&#xff1a;有效和系统的软件测试第2章&#xff1a;基于需求规格的测试第3章&#xff1a;结构化测试与代码覆盖第4章&#xff1a;契约式设计第5章&#xff1a;基于属性的测试第6章&#xff1a;测试替身和模拟对象第7章&#xff1a;…

Python综合案例(基本地图使用)

一、基本地图的使用 基本代码&#xff1a; """ 演示地图可视化的基本使用 """ from pyecharts.charts import Map from pyecharts.options import VisualMapOpts# 准备地图对象 map Map() # 准备数据 data [("北京", 99),("…

【C++进阶(四)】STL大法--list深度剖析list迭代器问题探讨

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 链表list 1. 前言2. list的使用2.1 list的构造函…

【树形权限】树形列表权限互斥选择、el-tree设置禁用等等

文章目录 一、实现如上树形列表1.1 首先要就是渲染树形列表1.2 然后通过插槽处理头部标题1.3 再通过插槽处理表格body体内容1.4 让body体中的选框和表头中的选框产生关联 二、将 el-tree 整棵树设为禁用状态三、动态表格合并 需求&#xff1a;按照权限管理配置的数据权限树展开…

如何利用客户旅程打造好的用户体验?

在当今竞争激烈的市场中&#xff0c;提供卓越的用户体验已经成为企业脱颖而出的关键因素之一。客户旅程是实现出色用户体验的有力工具之一&#xff0c;而HubSpot的客户旅程规划功能为企业提供了强大的支持&#xff0c;帮助他们更好地理解、管理和改善客户的互动过程。今天运营坛…

【USRP】调制解调系列5:16QAM、32QAM、64QAM、256QAM、1024QAM、基于labview的实现

QAM 正交振幅键控是一种将两种调幅信号&#xff08;2ASK和2PSK&#xff09;汇合到一个信道的方法&#xff0c;因此会双倍扩展有效带宽&#xff0c;正交调幅被用于脉冲调幅。正交调幅信号有两个相同频率的载波&#xff0c;但是相位相差90度&#xff08;四分之一周期&#xff0c…

参编三大金融国标,奇富科技以技术促行业规范化演进

近期&#xff0c;由中国互联网金融协会领导制定的《互联网金融智能风险防控技术要求》《互联网金融个人网络消费信贷信息披露》《互联网金融个人身份识别技术要求》三项国家标准颁布&#xff0c;由国家市场监督管理总局、国家标准化管理委员会发布&#xff0c;奇富科技作为核心…

Flutter 混合开发调试

针对Flutter开发的同学来说&#xff0c;大部分的应用还是Native Flutter的混合开发&#xff0c;所以每次改完Flutter代码&#xff0c;运行整个项目无疑是很费时间的。所以Flutter官方也给我们提供了混合调试的方案【在混合开发模式下进行调试】&#xff0c;这里以Android Stud…

OPENCV实现图像查找

特征匹配+单应性矩阵 # -*- coding:utf-8 -*- """ 作者:794919561 日期:2023/9/4 """ import cv2 import numpy as np# 读图像 img1 = cv2.imread(F:\\learnOpenCV\\openCVLearning\\pictures\\chess

【HTML5高级第一篇】Web存储 - cookie、localStorage、sessionStorage

文章目录 一、数据存储1.1 cookie1.1.1 概念介绍1.1.2 存储与获取1.1.3 方法的封装1.1.4 总结 1.2 localstorage 与 sessionstorage1.2.1 概述1.2.2 操作数据的属性或方法1.2.3 案例-提交问卷1.2.4 Web Storage带来的好处 附录&#xff1a;1. HTML5提供的数据持久化技术&#x…

万里路,咫尺间:汽车与芯片的智能之遇

目前阶段&#xff0c;汽车产业有两个最闪耀的关键词&#xff0c;就是智能与低碳。 在践行双碳目标与产业智能化的大背景下&#xff0c;汽车已经成为了能源技术、交通技术、先进制造以及通信、数字化、智能化技术的融合体。汽车的产品形态与产业生态都在发生着前所未有的巨大变革…

Hadoop的概述与安装

Hadoop的概述与安装 一、Hadoop内部的三个核心组件1、HDFS&#xff1a;分布式文件存储系统2、YARN&#xff1a;分布式资源调度系统3、MapReduce&#xff1a;分布式离线计算框架4、Hadoop Common&#xff08;了解即可&#xff09; 二、Hadoop技术诞生的一个生态圈数据采集存储数…

C语言:递归思想及实例详解

简介&#xff1a;在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。通过函数的自调用化繁为简。 递归可以说是编程中最神奇的一种算法。因为我们有时候可能不能完全明晰代码的运行过程&#xff0c;但是我们却知道代码可以跑出正确的结果。而当我们使…

qt day 6

登录界面 #include "window.h" #include<QDebug> #include<QIcon> Window::Window(QWidget *parent) //构造函数的定义: QWidget(parent) //显性调用父类的构造函数 {//判断数据库对象是否包含了自己使用的数据库Student.dbif(!db.contains(&…

微波系统中散射参量S、阻抗参量Z及导纳参量Y之间的关系及MATLAB验证

微波系统中散射参量S、阻抗参量Z及导纳参量Y之间的关系及MATLAB验证 用HFSS设计了一微波元件&#xff0c;仿真出了其散射参量S、阻抗参量Z及导纳参量Y&#xff0c;用MATLAB验证他们之间的关系 HFSS设计螺旋线圈 用HFSS设计了一个螺旋线圈&#xff0c;如上图所示。 进行仿真&…

8K视频来了,8K 视频编辑的最低系统要求

当今 RED、Canon、Ikegami、Sony 等公司的 8K 摄像机以及 8K 电视&#xff0c;许多视频内容制作人和电影制作人正在认真考虑 8K 拍摄、编辑和后期处理&#xff0c;需要什么样的系统来处理如此海量的数据&#xff1f; 中央处理器&#xff08;CPU&#xff09; 首先&#xff0c;…

Spring Security安全登录的调用过程以及获取权限的调用过程

1.第一次登录时候调用/user/login整个流程分析 (0)权限授理 首先调用SecurityConfig.java中的config函数将jwtAuthenticationTokenFilter过滤器放在UsernamePasswordAuthenticationFilter之前 Override protected void configure(HttpSecurity http) throws Exception{......…

FinClip 支持创建 H5应用类小程序;PC 终端 优化升级

FinClip 的使命是使您能够通过小程序解决关键业务流程挑战&#xff0c;并完成数字化转型。不妨让我们看看本月产品与市场发布亮点&#xff0c;是否有助于您实现目标。 产品方面的相关动向&#x1f447;&#x1f447;&#x1f447; FinClip 支持创建 H5应用类小程序 近期我们…