文章目录
- 一、目标: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<>());
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
}
3.4.4 静态SQL源码实现类
StaticSqlSource.java
package com.lino.mybatis.builder;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.session.Configuration;
import java.util.List;
/**
* @description: 静态sql源码
*/
public class StaticSqlSource implements SqlSource {
private String sql;
private List<ParameterMapping> parameterMappings;
private Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
3.5 动态上下文
DynamicContext.java
package com.lino.mybatis.scripting.xmltags;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 动态上下文
*/
public class DynamicContext {
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
static {
// 定义属性->getter方法映射,ContextMap到ContextAccessor的映射,注册到ognl运行时
// 参考http://commons.apache.org/proper/commons-ognl/developer-guide.html
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
// 将传入的参数对象统一封装为ContextMap对象(继承了HashMap对象),
// 然后Ognl运行时环境在动态计算sql语句时,
// 会按照ContextAccessor中描述的Map接口的方式来访问和读取ContextMap对象,获取计算过程中需要的参数。
// ContextMap对象内部可能封装了一个普通的POJO对象,也可以是直接传递的Map对象,当然从外部是看不出来的,因为都是使用Map的接口来读取数据。
}
private final ContextMap bindings;
private final StringBuilder sqlBuilder = new StringBuilder();
private int uniqueNumber;
/**
* 在DynamicContext的构造函数中,根据传入的参数对象是否为Map类型,有两个不同构造ContextMap的方式。
* 而ContextMap作为一个继承了HashMap的对象,作用就是用于统一参数的访问方式;用Map接口方法来访问数据。
* 具体来说,当传入的参数对象不是Map类型时,Mybatis会将传入的POJO对象用MetaObject对象来封装。
* 当动态计算sql过程需要获取数据时,用Map接口的get方法包装 MetaObject对象的取值过程。
*
* @param configuration 配置项
* @param parameterObject 参数对象
*/
public DynamicContext(Configuration configuration, Object parameterObject) {
// 绝大多数调用的地方 parameterObject 为null
if (parameterObject != null && !(parameterObject instanceof Map)) {
// 如果是map型 ?? 这句是 如果不是map型
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
public Map<String, Object> getBindings() {
return bindings;
}
public void bind(String name, Object value) {
bindings.put(name, value);
}
public void appendSql(String sql) {
sqlBuilder.append(sql);
sqlBuilder.append(" ");
}
public String getSql() {
return sqlBuilder.toString().trim();
}
public int getUniqueNumber() {
return uniqueNumber++;
}
/**
* 上下文map, 静态内部类
*/
static class ContextMap extends HashMap<String, Object> {
private static final long serialVersionUID = 2977601501966151582L;
private MetaObject parameterMetaObject;
public ContextMap(MetaObject parameterMetaObject) {
this.parameterMetaObject = parameterMetaObject;
}
@Override
public Object get(Object key) {
String strKey = (String) key;
// 先去map中找
if (super.containsKey(strKey)) {
return super.get(strKey);
}
// 如果没找到,再用ognl表达式去取值
// 如person[0].birthdate.year
if (parameterMetaObject != null) {
return parameterMetaObject.getValue(strKey);
}
return null;
}
}
static class ContextAccessor implements PropertyAccessor {
@Override
public Object getProperty(Map context, Object target, Object name) throws OgnlException {
Map map = (Map) target;
Object result = map.get(name);
if (result != null) {
return result;
}
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
if (parameterObject instanceof Map) {
return ((Map) parameterObject).get(name);
}
return null;
}
@Override
public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
Map<Object, Object> map = (Map<Object, Object>) target;
map.put(name, value);
}
@Override
public String getSourceAccessor(OgnlContext ognlContext, Object o, Object o1) {
return null;
}
@Override
public String getSourceSetter(OgnlContext ognlContext, Object o, Object o1) {
return null;
}
}
}
3.6 SQL节点
3.6.1 SQL节点接口
SqlNode.java
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;
}
@Override
public boolean apply(DynamicContext context) {
// 依次调用list里每个元素的apply
contexts.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;
}
@Override
public 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 {
@Override
public SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {
// 用XML脚本构建器解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
}
- 关于 XML 语言驱动器的实现较简单,只是封装了对 XMLScriptBuilder 的调用处理。
3.7.3 脚本语言注册器
LanguageDriverRegistry.java
package com.lino.mybatis.scripting;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 脚本语言注册器
*/
public class LanguageDriverRegistry {
/**
* 脚本语言Map
*/
private final Map<Class<?>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>(16);
private Class<?> defaultDriverClass = null;
public void register(Class<?> cls) {
if (cls == null) {
throw new IllegalArgumentException("null is not a valid Language Driver");
}
if (!LanguageDriver.class.isAssignableFrom(cls)) {
throw new RuntimeException(cls.getName() + " does not implements" + LanguageDriver.class.getName());
}
// 如果没注册过,再去注册
LanguageDriver driver = LANGUAGE_DRIVER_MAP.get(cls);
if (driver == null) {
try {
// 单例模式,即一个Class只有一个对应的LanguageDriver
driver = (LanguageDriver) cls.newInstance();
LANGUAGE_DRIVER_MAP.put(cls, driver);
} catch (Exception e) {
throw new RuntimeException("Failed to load language driver for " + cls.getName(), e);
}
}
}
public LanguageDriver getDriver(Class<?> cls) {
return LANGUAGE_DRIVER_MAP.get(cls);
}
public LanguageDriver getDefaultDriver() {
return getDriver(getDefaultDriverClass());
}
public Class<?> getDefaultDriverClass() {
return defaultDriverClass;
}
/**
* Configuration()有调用, 默认为XMLLanguageDriver
*
* @param defaultDriverClass 默认驱动类型
*/
public void setDefaultDriverClass(Class<?> defaultDriverClass) {
register(defaultDriverClass);
this.defaultDriverClass = defaultDriverClass;
}
}
3.8 类型处理器
3.8.1 类型处理器接口
TypeHandler.java
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:VARCHAR
if (left < expression.length()) {
// 首先,得到逗号或者冒号之前的字符串,加入到property
int right = skipUntil(expression, left, ",:");
put("property", trimmedStr(expression, left, right));
// 第二,处理javaType,jdbcType
jdbcTypeOpt(expression, right);
}
}
private int skipWS(String expression, int p) {
for (int i = p; i < expression.length(); i++) {
if (expression.charAt(i) > 0x20) {
return i;
}
}
return expression.length();
}
private int skipUntil(String expression, int p, String endChars) {
for (int i = p; i < expression.length(); i++) {
char c = expression.charAt(i);
if (endChars.indexOf(c) > -1) {
return i;
}
}
return expression.length();
}
private void jdbcTypeOpt(String expression, int p) {
// #{property,javaType=int,jdbcType=NUMERIC}
// property:VARCHAR
// 首先去除空白,返回的p是第一个不是空白的字符位置
p = skipWS(expression, p);
if (p < expression.length()) {
// 第一个property解析完有两种情况,逗号和冒号
if (expression.charAt(p) == ':') {
jdbcType(expression, p + 1);
} else if (expression.charAt(p) == ',') {
option(expression, p + 1);
} else {
throw new RuntimeException("Parsing error in {" + new String(expression) + "} in position " + p);
}
}
}
private void jdbcType(String expression, int p) {
// property:VARCHAR
int left = skipWS(expression, p);
int right = skipUntil(expression, left, ",");
if (right > left) {
put("jdbcType", trimmedStr(expression, left, right));
} else {
throw new RuntimeException("Parsing error in {" + new String(expression) + "} in position " + p);
}
option(expression, right + 1);
}
private void option(String expression, int p) {
// #{property,javaType=int,jdbcType=NUMERIC}
int left = skipWS(expression, p);
if (left < expression.length()) {
int right = skipUntil(expression, left, "=");
String name = trimmedStr(expression, left, right);
left = right + 1;
right = skipUntil(expression, left, ",");
String value = trimmedStr(expression, left, right);
put(name, value);
// 递归调用option,进行逗号后面一个属性的解析
option(expression, right + 1);
}
}
private String trimmedStr(String str, int start, int end) {
while (str.charAt(start) <= 0x20) {
start++;
}
while (str.charAt(end - 1) <= 0x20) {
end--;
}
return start >= end ? "" : str.substring(start, end);
}
}
3.11 修改映射器语句和参数映射
3.11.1 修改映射器语句
MappedStatement.java
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 拿到 SQL
String 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);
// 返回静态SQL
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
private Class<?> parameterType;
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
/**
* 构建参数映射
*
* @param content 参数
* @return 参数映射
*/
private ParameterMapping buildParameterMapping(String content) {
// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}
Map<String, String> propertiesMap = new ParameterExpression(content);
String property = propertiesMap.get("property");
Class<?> propertyType = parameterType;
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
return builder.build();
}
}
}
- 在上一章中,关于
BoundSql.parameterMappings
的参数就是来自于ParameterMappingTokenHandler#buildParameterMapping
方法进行构建处理的。 - 具体的
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();
// 添加解析 SQL
configuration.addMappedStatement(mappedStatement);
}
}
- 这部分内容的解析,就是从 XMLConfigBuilder 拆解出来关于 Mapper 语句解析的部分,通过这样的解耦设计,会让整个流程更加清晰。
XMLStatementBuilder#parseStatementNode
方法是解析 SQL 语句节点的过程,包括:语句的ID、参数类型、结果类型、命令(select|insert|update|delete
),以及使用语言驱动器处理和封装 SQL 信息。- 当解析完成后写入到 Confiuration 配置文件中的
Map<String, MappedStatement>
映射语句存放中。
3.14.5 XML映射构建器,解耦映射解析
XMLMapperBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @description: XML映射构建器
*/
public class XMLMapperBuilder extends BaseBuilder {
private Element element;
private String resource;
private String currentNamespace;
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) throws DocumentException {
this(new SAXReader().read(inputStream), configuration, resource);
}
public XMLMapperBuilder(Document document, Configuration configuration, String resource) {
super(configuration);
this.element = document.getRootElement();
this.resource = resource;
}
/**
* 解析
*
* @throws Exception 异常
*/
public void parse() throws Exception {
// 如果当前资源没有加载过再加载,防止重复加载
if (!configuration.isResourceLoaded(resource)) {
configurationElement(element);
// 标记一下,已经加载过了
configuration.addLoadedResource(resource);
// 绑定映射器到namespace
configuration.addMapper(Resources.classForName(currentNamespace));
}
}
/**
* 配置mapper元素
* <mapper namespace="org.mybatis.example.BlogMapper">
* <select id="selectBlog" parameterType="int" resultType="Blog">
* select * from Blog where id = #{id}
* </select>
* </mapper>
*
* @param element 元素
*/
private void configurationElement(Element element) {
// 1.配置namespace
currentNamespace = element.attributeValue("namespace");
if ("".equals(currentNamespace)) {
throw new RuntimeException("Mapper's namespace cannot be empty");
}
// 2.配置select|insert|update|delete
buildStatementFromContext(element.elements("select"));
}
/**
* 配置select|insert|update|delete
*
* @param list 元素列表
*/
private void buildStatementFromContext(List<Element> list) {
for (Element element : list) {
final XMLStatementBuilder statementBuilder = new XMLStatementBuilder(configuration, element, currentNamespace);
statementBuilder.parseStatementNode();
}
}
}
- 在
XMLMapperBuilder#parse
的解析中,主要体现在资源解析判断、Mapper 解析和绑定映射器。configuration.isResourceLoaded
资源判断避免重复解析,做了一个记录。configuration.addMapper
绑定映射器:主要是把 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.调用父类初始化Configuration
super(new Configuration());
// 2.dom4j 处理xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
root = document.getRootElement();
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
*
* @return Configuration
*/
public Configuration parse() {
try {
// 环境
environmentsElement(root.element("environments"));
// 解析映射器
mapperElement(root.element("mappers"));
} catch (Exception e) {
throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
return configuration;
}
/**
* <environments default="development">
* <environment id="development">
* <transactionManager type="JDBC">
* <property name="..." value="..."/>
* </transactionManager>
* <dataSource type="POOLED">
* <property name="driver" value="${driver}"/>
* <property name="url" value="${url}"/>
* <property name="username" value="${username}"/>
* <property name="password" value="${password}"/>
* </dataSource>
* </environment>
* </environments>
*/
private void environmentsElement(Element context) throws Exception {
String environment = context.attributeValue("default");
List<Element> environmentList = context.elements("environment");
for (Element e : environmentList) {
String id = e.attributeValue("id");
if (environment.equals(id)) {
// 事务管理器
TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();
// 数据源
Element dataSourceElement = e.element("dataSource");
DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
List<Element> propertyList = dataSourceElement.elements("property");
Properties props = new Properties();
for (Element property : propertyList) {
props.setProperty(property.attributeValue("name"), property.attributeValue("value"));
}
dataSourceFactory.setProperties(props);
DataSource dataSource = dataSourceFactory.getDataSource();
// 构建环境
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
/**
* <mappers>
* <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
* <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
* <mapper resource="org/mybatis/builder/PostMapper.xml"/>
* </mappers>
*/
private void mapperElement(Element mappers) throws Exception {
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
String resource = e.attributeValue("resource");
InputStream inputStream = Resources.getResourceAsStream(resource);
// 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
mapperParser.parse();
}
}
}
- 在
XMLConfigBuilder#mapperElement
中,把原来的流程化的处理进行解耦, 调用XMLMapperBuilder#parse
方法进行解析处理。
3.15 DefaultSqlSession 调用调整
DefaultSqlSession.java
package com.lino.mybatis.session.defaults;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.util.List;
/**
* @description: 默认sqlSession实现类
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
MappedStatement ms = configuration.getMappedStatement(statement);
List<T> list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));
return list.get(0);
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
}
- 这里调整不大,主要体现在获取 SQL 的操作上。
ms.getSqlSource().getBoundSql(parameter)
。
四、测试:XML语句构建器
ApiText.java
@Test
public void test_SqlSessionFactoryExecutor() throws IOException {
// 1.从SqlSessionFactory中获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
// 2.获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 3.测试验证
User user = userDao.queryUserInfoById(1L);
logger.info("测试结果:{}", JSON.toJSONString(user));
}
测试结果
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 语句。