文章目录
- 一、目标: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);}
}
- 将
getResourceAsStream
从 private 改为 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个实现类:
XMLLanguageDriver
、RawLanguageDriver
、VelocityLanguageDriver
,这里只实现了第一种实现。
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;}
}
- 修改 BoundSql 为 SqlSource。
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
方法进行构建处理的。 - 具体的
javaType
、jdbcType
会体现到 ParameterExpression 参数表达式中完成解析操作。
3.14.4 XML语言构建器
XMLStatementBuilder
语句构建器主要解析 XML 中select|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
绑定映射器:主要是把 namespacecom.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 语句。