手写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.rhkb.cn/news/121165.html

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

相关文章

《TCP/IP网络编程》阅读笔记--地址族和数据序列

目录 1--IP地址和端口号 2--地址信息的表示 3--网络字节序与地址变换 4--网络地址的初始化与分配 5--Windows部分代码案例 1--IP地址和端口号 IP 地址分为两类&#xff1a; ① IPv4 表示 4 字节地址族&#xff1b; ② IPv6 表示 16 字节地址族&#xff1b; IPv4 标准的 4 …

B081-Lucene+ElasticSearch

目录 认识全文检索概念lucene原理全文检索的特点常见的全文检索方案 Lucene创建索引导包分析图代码 搜索索引分析图代码 ElasticSearch认识ElasticSearchES与Kibana的安装及使用说明ES相关概念理解和简单增删改查ES查询DSL查询DSL过滤 分词器IK分词器安装测试分词器 文档映射(字…

使用ELK收集解析nginx日志和kibana可视化仪表盘

文章目录 ELK生产环境配置filebeat 配置logstash 配置 kibana仪表盘配置配置nginx转发ES和kibanaELK设置账号和密码 ELK生产环境配置 ELK收集nginx日志有多种方案&#xff0c;一般比较常见的做法是在生产环境服务器搭建filebeat 收集nginx的文件日志并写入到队列&#xff08;k…

uniapp - 倒计时组件-优化循环时间倒计时

使用定时器的规避方法 为了避免定时器误差导致倒计时计算错误&#xff0c;可以采用一些规避方法&#xff0c;比如将倒计时被中断时的剩余时间记录下来&#xff0c;重新开启定时器时再将这个剩余时间加到新的计算中。同时&#xff0c;为了避免定时器延迟&#xff0c;可以在每次执…

Python数据分析实战-Series转DataFrame并将index设为新的一列(附源码和实现效果)

实现功能 Series转DataFrame并将index设为新的一列 实现代码 import pandas as pd# 创创建series series pd.Series([1, 2, 3, 4, 5])# 创建一个DataFrame对象 data {column_name: series} df pd.DataFrame(data)# 重新设置索引&#xff0c;将原有的索引作为新的一列 df.r…

GIT实战篇,教你如何使用GIT可视化工具

系列文章目录 手把手教你安装Git&#xff0c;萌新迈向专业的必备一步 GIT命令只会抄却不理解&#xff1f;看完原理才能事半功倍&#xff01; 快速上手GIT命令&#xff0c;现学也能登堂入室 GIT实战篇&#xff0c;教你如何使用GIT可视化工具 系列文章目录一、GIT有哪些常用工具…

2023高教社杯数学建模A题B题C题D题E题思路模型 国赛建模思路分享

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 全国大学生数学建模…

如何截取视频中的一段视频?分享几种视频分割方法

当处理长视频时&#xff0c;视频分割可以使您更加高效。如果您只需要处理其中的一部分&#xff0c;而不是整个视频&#xff0c;那么分割视频可以使您更容易找到需要处理的部分。而且&#xff0c;分割视频还可以使您更容易在不同的项目之间重复使用视频片段。教大家几种简单的视…

基于Python的IOS自动化测试环境搭建

文章目录 一、测试架构介绍1.1 WebDriverAgent原理分析1.2 tidevice原理分析二、环境安装2.1 iOS 设备安装 WebDriverAgent2.2 安装iTunes2.3 安装tidevice2.4 安装facebook-wda自动化三、操作流程四、Weditor的安装和使用一、测试架构介绍 以下为测试架构原理图 手机端的WDA…

【vue2第十二章】ref和$refs获取dom元素 和 vue异步更新与$nextTick使用

ref和$refs获取dom元素 为什么会有 ref 和 $refs&#xff1f; 因为在vue页面中使用dom查找元素&#xff0c;不管你是不是在子组件里面查找&#xff0c;查找的都是整个页面的元素&#xff0c;如果你想查找单独组件里面的元素是不容易实现的&#xff0c;除非把每个组件的class写…

【Java转Go】Go中使用WebSocket实现聊天室(私聊+群聊)

目录 前言功能效果&#xff08;一人分饰多角.jpg&#x1f60e;&#xff09;用户上线、群聊私聊和留言下线 实现思路代码服务端 chat.go 完整代码客户端 html 完整代码 最后 前言 之前在Java中&#xff0c;用 springbootwebsocket 实现了一个聊天室&#xff1a;springbootwebso…

【广州华锐互动】利用AR远程指导系统进行机械故障排查,实现远程虚拟信息互动

随着工业自动化和智能化的不断发展&#xff0c;机械故障诊断已经成为了工业生产中的重要环节。为了提高故障诊断的准确性和效率&#xff0c;近年来&#xff0c;AR&#xff08;增强现实&#xff09;远程协助技术逐渐应用于机械故障诊断领域。本文将探讨AR远程协助技术在机械故障…

华为数通方向HCIP-DataCom H12-821题库(单选题:201-220)

第201题 BGP 协议用​​ beer default-route-advertise​​ 命令来给邻居发布缺省路由,那么以下关于本地 BGP 路由表变化的描述&#xff0c;正确的是哪一项? A、在本地 BGP 路由表中生成一条活跃的缺省路由并下发给路由表 B、在本地 BGP 路由表中生成一条不活跃的缺省路由&…

应用于伺服电机控制、 编码器仿真、 电动助力转向、发电机、 汽车运动检测与控制的旋变数字转换器MS5905P

MS5905P 是一款 12bit 分辨率的旋变数字转换器。 片上集成正弦波激励电路&#xff0c;正弦和余弦允许输入峰峰值 幅度为 2.3V 到 4.0V &#xff0c;可编程激励频率为 10kHz 、 12kHz 、 15kHz 、 20kHz 。 转换器可并行或串行输出角度 和速度对应的数字量。 MS5905…

动态贴纸、美颜SDK与AR:创造独特的互动体验

目前&#xff0c;动态贴纸、美颜SDK、增强现实&#xff08;AR&#xff09;等技术是比较热门的话题&#xff0c;它们所结合的新兴玩法更是收到大家推崇&#xff0c;正潜移默化的改变我们与数字世界互动的方式。 一、动态贴纸&#xff1a;个性化互动的开始 动态贴纸&#xff0c…

JVM-CMS

when 堆大小要求为4-8G 原理 初始标记&#xff1a;执行CMS线程->STW&#xff0c;标记GC Root直接关联的对象->低延迟 并发标记&#xff1a;执行CMS线程和业务线程&#xff0c;从GC Root直接关联的对象开始遍历整个对象图 重新标记&#xff1a;执行CMS线程->STW&a…

大数据时代下的数据安全防护

随着大数据时代的来临&#xff0c;数据安全防护成为了一个重要的问题。在大数据时代&#xff0c;数据的规模和价值都得到了极大的提升&#xff0c;因此数据安全的重要性也变得越来越突出。本文将从数据加密、访问控制、网络安全和人员管理四个方面来介绍大数据时代下的数据安全…

CVE-2023-23752:Joomla未授权访问漏洞复现

CVE-2023-23752&#xff1a;Joomla未授权访问漏洞复现 前言 本次测试仅供学习使用&#xff0c;如若非法他用&#xff0c;与本文作者无关&#xff0c;需自行负责&#xff01;&#xff01;&#xff01; 一.Openfire简介 Joomla是一个免费的开源内容管理系统&#xff08;CMS&a…

Unity中Shader的UV扭曲效果的实现

文章目录 前言一、实现的思路1、在属性面板暴露一个 扭曲贴图的属性2、在片元结构体中&#xff0c;新增一个float2类型的变量&#xff0c;用于独立存储将用于扭曲的纹理的信息3、在顶点着色器中&#xff0c;根据需要使用TRANSFORM_TEX对Tilling 和 Offset 插值&#xff1b;以及…

群晖NAS:DS Video、Jellyfin等视频电影电视剧海报、背景墙搜刮器

群晖NAS&#xff1a;DS Video、Jellyfin等视频电影电视剧海报、背景墙搜刮器 本文只使用豆瓣插件方式&#xff0c;系统默认的 The Movie Database 好注册&#xff0c;但是授权码输入后域名不通过&#xff0c;很麻烦。 1、插件地址&#xff1a; https://www.aliyundrive.com/s…