文章目录
- 一、目标:参数处理器
- 二、设计:参数处理器
- 三、实现:参数处理器
- 3.1 工程结构
- 3.2 参数处理器关系图
- 3.3 入参数校准
- 3.4 参数策略处理器
- 3.4.1 JDBC枚举类型修改
- 3.4.2 类型处理器接口
- 3.4.3 模板模式:类型处理器抽象基类
- 3.4.4 类型处理器具体的子类实现
- 3.4.5 类型处理器注册机
- 3.5 参数构建
- 3.5.1 参数映射类的修改
- 3.5.2 SQL源码构建器
- 3.6 参数使用
- 3.6.1 参数处理器接口
- 3.6.2 默认参数处理器
- 3.7 参数处理器的使用
- 3.7.1 脚本语言驱动
- 3.7.2 XML语言驱动器
- 3.7.3 映射器语句类修改,添加脚本语言驱动
- 3.7.4 修改配置类
- 3.7.5 语句处理器抽象基类
- 3.7.6 预处理语句处理器
- 3.7.7 默认sqlSession实现类添加日志描述
- 四、测试:参数处理器
- 4.1 修改 DAO 接口
- 4.2 修改 User 实体类
- 4.3 配置Mapper文件
- 4.4 单元测试
- 4.4.1 提取初始化SqlSession
- 4.4.2 基本类型参数
- 4.4.3 对象类型参数
- 五、总结:参数处理器
一、目标:参数处理器
💡 结合参数的提取,对执行的 SQL 进行参数的自动化设置,而不是像我们之前那样把参数写成固定的。
- 在流程上,通过
DefaultSqlSession#selectOne
方法调用执行器,并通过预处理语句处理器PreparedStatementHandler
执行参数设置和结果查询。 - 这个流程中我们处理的参数信息,也就是每个 SQL 执行时,那些
?号
需要被替换的地方,目前是通过硬编码的方式进行处理的。 - 使用策略模式,处理硬编码为自动化类型设置。
二、设计:参数处理器
💡 在 SQL 拆解出参数类型之后,怎么根据不同的参数进行不同的类型设置
- 在自动化解析 XML 中 SQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置
Long
调用ps.setLong
,String
调用ps.setString
- 这里使用 策略模式,封装进去类型处理器(也就是实现 TypeHandler 接口的过程)。
- 其实关于参数的处理,因为有很多的类型(
Long\String\Object\...
),所以这里最重要的体现则是 策略模式 的使用。- 包括:构建参数时根据类型,选择对应的策略类型处理器,填充到参数映射集合中。
- 另一方面:参数的使用,也就是在执行
DefaultSqlSession#selectOne
的链路中。- 包括:参数的设置,按照参数不同类型,获取出对应的处理器,以及入参。
- 注意:由于入参值可能是一个对象中的属性,所以我们使用反射工具类 MetaObject 进行值的获取,避免由于动态的对象,没法硬编码获取属性值。
三、实现:参数处理器
3.1 工程结构
mybatis-step-09
|-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| | |-parameter| | | |-ParameterHandler.java| | |-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| | | |-DefaultParameterHandler.java| | | |-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| | |-BaseTypeHandler.java| | |-IntegerTypeHandler.java| | |-JdbcType.java| | |-LongTypeHandler.java| | |-StringTypeHandler.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 参数处理器关系图
- 策略模式,核心处理主要分为三块:类型处理、参数设置、参数使用。
- 定义
TypeHandler
类型处理器策略接口,实现不同的处理策略,包括:Long、String、Integer
等。 - 类型策略处理器实现完成后,需要注册到处理器注册机中,后续其他模块参数的设置的使用都是从 Configuration 中获取到 TypeHandlerRegistry 进行使用。
- 有了策略处理器之后,在进行操作解析 SQL 时,就可以按照不同的类型把对应的策略处理器设置到
BoundSql#parameterMappings
参数里。
- 定义
3.3 入参数校准
💡 针对参数的传递?
- 这里的参数传递后,需要获取第0个参数,而且是硬编码固定。
- 这是为什么呢?这个第0个参数是哪来的,我们接口调用的方法,参数不是一个吗?就像
User queryUserInfoById(Long id)
.
- 这是为什么呢?这个第0个参数是哪来的,我们接口调用的方法,参数不是一个吗?就像
- 其实这个参数来自映射器代理类
MapperProxy#invoke
中,因为invoke
反射调用的方法,入参中是Object[] args
,所以这个参数被传递到后续的参数设置中。而我们的 DAO 测试类是一个已知的固定参数,所以后面硬编码获取了第0个参数。- JDK 反射调用方法操作固定方法入参。
- 现在我们需要根据方法的信息,给方法做签名操作,以便于转换入参信息为方法的信息。比如:数组转换为对应的对象。
MapperMethod.java
package com.lino.mybatis.binding;import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import com.mysql.fabric.xmlrpc.base.Param;
import javassist.bytecode.SignatureAttribute;
import java.lang.reflect.Method;
import java.util.*;/*** @description: 映射器方法*/
public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {this.command = new SqlCommand(configuration, mapperInterface, method);this.method = new MethodSignature(configuration, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result = null;switch (command.getType()) {case INSERT:break;case DELETE:break;case UPDATE:break;case SELECT:Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);break;default:throw new RuntimeException("Unknown execution method for: " + command.getName());}return result;}/*** SQL 指令*/public static class SqlCommand {private final String name;private final SqlCommandType type;public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {String statementName = mapperInterface.getName() + "." + method.getName();MappedStatement ms = configuration.getMappedStatement(statementName);this.name = ms.getId();this.type = ms.getSqlCommandType();}public String getName() {return name;}public SqlCommandType getType() {return type;}}/*** 方法签名*/public static class MethodSignature {private final SortedMap<Integer, String> params;public MethodSignature(Configuration configuration, Method method) {this.params = Collections.unmodifiableSortedMap(getParams(method));}public Object convertArgsToSqlCommandParam(Object[] args) {final int paramCount = params.size();if (args == null || paramCount == 0) {// 如果没参数return null;} else if (paramCount == 1) {return args[params.keySet().iterator().next().intValue()];} else {// 否则,返回一个ParamMap, 修改参数名,参数名就是其位置final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {// 1.先加一个#{0},#{1},#{2}...参数param.put(entry.getValue(), args[entry.getKey().intValue()]);// issue #71, add param names as param1, param2...but ensure backward compatibilityfinal String genericParamName = "param" + (i + 1);if (!param.containsKey(genericParamName)) {/*2.再加一个#{param1},#{param2}...参数你可以传递多个参数给一个映射器方法。默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等如果你想改变参数的名称(只在多参数情况下),那么你可以在参数上使用@Param("paramName")注解*/param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}private SortedMap<Integer, String> getParams(Method method) {// 用一个TreeMap,这样就保证还是按参数的先后顺序final SortedMap<Integer, String> params = new TreeMap<>();final Class<?>[] argTypes = method.getParameterTypes();for (int i = 0; i < argTypes.length; i++) {String paramName = String.valueOf(params.size());// 不做 Param 的实现,这部分不处理。params.put(i, paramName);}return params;}/*** 参数map,静态内部类,更严格的get方法,如果没有相应的key,报错*/public static class ParamMap<V> extends HashMap<String, V> {private static final long serialVersionUID = -2212268410512043556L;@Overridepublic V get(Object key) {if (!super.containsKey(key)) {throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());}return super.get(key);}}}
}
- 在映射器方法中
MapperMethod#execute
将原来的直接将参数 args 传递给SqlSession#selectOne
方法,调整为转换后再传递对象。 - 这里的转换操作就是来自于
Method#getParameterTypes
对参数的获取和处理,于 args 进行对比。- 如果是单个参数,则直接返回参数 Tree 树结构下的对应节点值。
- 非单个类型,则需要进行循环处理,这样转换后的参数才能被直接使用。
3.4 参数策略处理器
- Mybatis 的源码包中,type 包下所提供的就是一套参数的处理策略集合。
- 它通过定义类型处理器接口、由抽象模板实现并定义标准流程,定义提取抽象方法交予给子类实现,这些之类就是各个类型处理器的具体实现。
3.4.1 JDBC枚举类型修改
JdbcType.java
package com.lino.mybatis.type;import java.sql.Types;
import java.util.HashMap;
import java.util.Map;/*** @description: JDBC枚举类型* @author: lingjian* @createDate: 2022/11/5 17:10*/
public enum JdbcType {// JDBC枚举类型INTEGER(Types.INTEGER),FLOAT(Types.FLOAT),DOUBLE(Types.DOUBLE),DECIMAL(Types.DECIMAL),VARCHAR(Types.VARCHAR),CHAR(Types.CHAR),TIMESTAMP(Types.TIMESTAMP);public final int TYPE_CODE;private static Map<Integer, JdbcType> codeLookup = new HashMap<>();static {for (JdbcType type : JdbcType.values()) {codeLookup.put(type.TYPE_CODE, type);}}JdbcType(int code) {this.TYPE_CODE = code;}public static JdbcType forCode(int code) {return codeLookup.get(code);}
}
- 添加
CHAR(Typcs.CHAR)
字符类型
3.4.2 类型处理器接口
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.4.3 模板模式:类型处理器抽象基类
BaseTypeHandler.java
package com.lino.mybatis.type;import com.lino.mybatis.session.Configuration;
import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 类型处理器的基类*/
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {protected Configuration configuration;public void setConfiguration(Configuration configuration) {this.configuration = configuration;}@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {// 定义抽象方法,由子类实现不同类型的属性设置setNonNullParameter(ps, i, parameter, jdbcType);}/*** 属性设置:抽象方法,由子类实现** @param ps 预处理语言* @param i 次数* @param parameter 参数对象* @param jdbcType JDBC类型* @throws SQLException SQL异常*/protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
- 通过抽象基类的流程模板定义,便于一些参数的判断和处理。
3.4.4 类型处理器具体的子类实现
IntegerTypeHandler.java
package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: Integer类型处理器*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter);}
}
LongTypeHandler.java
package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: Long类型处理器*/
public class LongTypeHandler extends BaseTypeHandler<Long> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {ps.setLong(i, parameter);}
}
StringTypeHandler.java
package com.lino.mybatis.type;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: String类型处理器*/
public class StringTypeHandler extends BaseTypeHandler<String> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter);}
}
- 这里的接口实现暂时实现了3个,分别是
IntegerTypeHandler、LongTypeHandler、StringTypeHandler
。 - 在 Mybatis 中源码还有30+个其他类型,可以参照源码阅读。
3.4.5 类型处理器注册机
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() {register(Long.class, new LongTypeHandler());register(long.class, new LongTypeHandler());register(Integer.class, new IntegerTypeHandler());register(int.class, new IntegerTypeHandler());register(String.class, new StringTypeHandler());register(String.class, JdbcType.CHAR, new StringTypeHandler());register(String.class, JdbcType.VARCHAR, new StringTypeHandler());}private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {register(javaType, null, typeHandler);}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);}@SuppressWarnings("unchecked")public TypeHandler<?> getTypeHandler(Class<?> type, JdbcType jdbcType) {return getTypeHandler((Type) type, jdbcType);}public boolean hasTypeHandler(Class<?> javaType) {return hasTypeHandler(javaType, null);}public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;}private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);TypeHandler<?> handler = null;if (jdbcHandlerMap != null) {handler = jdbcHandlerMap.get(jdbcType);if (handler == null) {handler = jdbcHandlerMap.get(null);}}// type driver generics herereturn (TypeHandler<T>) handler;}
}
- 这里在构造函数中,新增加了
LongTypeHandler、IntegerTypeHandler、StringTypeHandler
三种类型的注册机。 - 同时注意到,无论是对象类型,还是基本类型,都是一个类型处理器。只不过在注册的时候多注册了一个。
- 这种操作方式和我们平常的业务开发中,也是一样的。一种是多注册,另外一种是判断处理。
3.5 参数构建
- 需要对 SqlSourceBuilder 源码构建器中,创建参数映射 ParameterMapping 需要添加参数处理器的内容。
- 因为只有这样才能方便的从参数映射中获取到对应类型的处理器进行使用。
- 需要完善 ParameterMappping 添加 TypeHandler 属性信息。
- 以及在
ParameterMappingTokenHandler#buildParameterMapping
处理参数映射时,构建出参数的映射。
- 以及在
3.5.1 参数映射类的修改
ParameterMapping.java
package com.lino.mybatis.mapping;import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.JdbcType;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;/*** @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 TypeHandler<?> typeHandler;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() {if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {Configuration configuration = parameterMapping.configuration;TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);}return parameterMapping;}}public Configuration getConfiguration() {return configuration;}public String getProperty() {return property;}public Class<?> getJavaType() {return javaType;}public JdbcType getJdbcType() {return jdbcType;}public TypeHandler<?> getTypeHandler() {return typeHandler;}
}
- 在 build 添加类型处理器的处理判断
3.5.2 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.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @description: SQL源码构建器*/
public class SqlSourceBuilder extends BaseBuilder {private static Logger logger = LoggerFactory.getLogger(SqlSourceBuilder.class);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;if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (property != null) {MetaClass metaClass = MetaClass.forClass(parameterType);if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);} else {propertyType = Object.class;}} else {propertyType = Object.class;}logger.info("构建参数映射 property:{} propertyType:{}", property, propertyType);ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);return builder.build();}}
}
- 这部分就是对参数的细化处理,构建出参数的映射关系。
- 首先是 if 判断对应的参数类型是否在 TypeHandlerRegistry 注册器中。
- 如果不在则拆解对象,按属性进行获取 propertyType 的操作。
- 这一块也用到了 MetaClass 反射工具类的使用。
3.6 参数使用
- 参数构建完成后,就可以在
DefaultSqlSession#selectOne
调用时设置参数使用了。- 链路:
Executor#query -> SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters
。 - 到
ParameterHandler#setParameters
就可以看到了根据参数的不同处理器循环设置参数。
- 链路:
3.6.1 参数处理器接口
ParameterHandler.java
package com.lino.mybatis.executor.parameter;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 参数处理器*/
public interface ParameterHandler {/*** 获取参数** @return 参数*/Object getParameterObject();/*** 设置参数** @param ps 预处理语句对象* @throws SQLException sql异常*/void setParameters(PreparedStatement ps) throws SQLException;
}
3.6.2 默认参数处理器
DefaultParameterHandler.java
package com.lino.mybatis.scripting.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.JdbcType;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;/*** @description: 默认参数处理器*/
public class DefaultParameterHandler implements ParameterHandler {private Logger logger = LoggerFactory.getLogger(DefaultParameterHandler.class);private final TypeHandlerRegistry typeHandlerRegistry;private final MappedStatement mappedStatement;private final Object parameterObject;private BoundSql boundSql;private Configuration configuration;public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {this.mappedStatement = mappedStatement;this.configuration = mappedStatement.getConfiguration();this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();this.parameterObject = parameterObject;this.boundSql = boundSql;}@Overridepublic Object getParameterObject() {return parameterObject;}@Overridepublic void setParameters(PreparedStatement ps) throws SQLException {List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (null != parameterMappings) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);String propertyName = parameterMapping.getProperty();Object value;if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 通过 MetaObject.getValue 反射取得值设置进去MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}JdbcType jdbcType = parameterMapping.getJdbcType();// 设置参数logger.info("根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:{}", JSON.toJSONString(value));TypeHandler typeHandler = parameterMapping.getTypeHandler();typeHandler.setParameter(ps, i + 1, value, jdbcType);}}}
}
- 每一个循环的参数设置,都是从 BoundSql 中获取 ParameterMapping 集合进行循环操作,而这个集合参数就是我们前面
ParameterMappingTokenHandler#buildParameterMapping
构建参数映射时处理的。 - 设置参数时根据参数的 parameterObject 入参信息,判断是否基本类型,如果不是则从对象中进行拆解获取(也就是一个对象A中包括对象B),处理完成后就可以准确拿到对应的入参值。
- 入参值在 MapperMethod 中已经处理了一遍 方法签名。
- 基本信息获取完成后,则根据参数类型获取到对应的 TypeHandler 类型处理器,也就是找到
IntegerTypeHandler、LongTypeHandler、StringTypeHandler
等,确定找到以后,则可以进行对应的参数设置。typeHandler.setParameter(ps, i + 1, value, jdbcType)
通过这样的方式把我们之前硬编码的操作进行解耦。
3.7 参数处理器的使用
3.7.1 脚本语言驱动
LanguageDriver.java
package com.lino.mybatis.scripting;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
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);/*** 创建参数处理器** @param mappedStatement 映射器语句类* @param parameterObject 参数对象* @param boundSql sql语句* @return ParameterHandler 参数处理器*/ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
}
- 添加创建参数处理器接口
3.7.2 XML语言驱动器
XMLLanguageDriver.java
package com.lino.mybatis.scripting.xmltags;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.defaults.DefaultParameterHandler;
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();}@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);}
}
3.7.3 映射器语句类修改,添加脚本语言驱动
MappedStatement.java
package com.lino.mybatis.mapping;import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;/*** @description: 映射器语句类*/
public class MappedStatement {private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class<?> resultType;private LanguageDriver lang;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;mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}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;}public LanguageDriver getLang() {return lang;}
}
3.7.4 修改配置类
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.parameter.ParameterHandler;
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.LanguageDriver;
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;}/*** 获取参数处理器** @param mappedStatement 映射器语言类型* @param parameterObject 参数对象* @param boundSql SQL语句* @return ParameterHandler参数处理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 创建参数处理器ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 获取默认脚本语言驱动** @return 脚本语言驱动*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}
}
- 添加获取默认脚本语言驱动
getDefaultScriptingLanguageInstance
- 添加获取参数处理器
newParameterHandler
3.7.5 语句处理器抽象基类
BaseStatementHandler.java
package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;/*** @description: 语句处理器抽象基类*/
public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final Executor executor;protected final MappedStatement mappedStatement;protected final Object parameterObject;protected final ResultSetHandler resultSetHandler;protected final ParameterHandler parameterHandler;protected BoundSql boundSql;public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.parameterObject = parameterObject;this.boundSql = boundSql;this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, boundSql);}@Overridepublic Statement prepare(Connection connection) {Statement statement = null;try {// 实例化 Statementstatement = instantiateStatement(connection);// 参数设置,可以被抽取,提供配置statement.setQueryTimeout(350);statement.setFetchSize(10000);return statement;} catch (Exception e) {throw new RuntimeException("Error prepare statement. Cause: " + e, e);}}protected abstract Statement instantiateStatement(Connection connection) throws SQLException;}
- 添加 ParameterHandler 参数处理器
3.7.6 预处理语句处理器
PreparedStatementHandler.java
package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 预处理语句处理器(PREPARED)*/
public class PreparedStatementHandler extends BaseStatementHandler {public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultSetHandler, BoundSql boundSql) {super(executor, mappedStatement, parameterObject, resultSetHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}
}
3.7.7 默认sqlSession实现类添加日志描述
DefaultSqlSession.java
package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;/*** @description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class);...@Overridepublic <T> T selectOne(String statement, Object parameter) {logger.info("执行查询 statement:{} parameter:{}", statement, JSON.toJSONString(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);}...
}
四、测试:参数处理器
4.1 修改 DAO 接口
IUserDao.java
package com.lino.mybatis.test.dao;import com.lino.mybatis.test.po.User;/*** @Description: 用户持久层*/
public interface IUserDao {/*** 根据ID查询用户信息** @param uId ID* @return User 用户*/User queryUserInfoById(Long uId);/*** 根据用户对象查询用户信息* @param user 用户* @return User 用户*/User queryUserInfo(User user);
}
4.2 修改 User 实体类
User.java
package com.lino.mybatis.test.po;import java.util.Date;/*** @description: 用户实例类*/
public class User {private Long id;/*** 用户ID*/private String userId;/*** 头像*/private String userHead;/*** 用户名称*/private String userName;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;public User() {}public User(Long id, String userId) {this.id = id;this.userId = userId;}// getter/setter
}
4.3 配置Mapper文件
User_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userWHERE id = #{id}</select><select id="queryUserInfo" parameterType="com.lino.mybatis.test.po.User" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userwhere id = #{id} and userId = #{userId}</select>
</mapper>
4.4 单元测试
4.4.1 提取初始化SqlSession
ApiTest.java
private SqlSession sqlSession;@Before
public void init() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));sqlSession = sqlSessionFactory.openSession();
}
- 因为接下来需要验证两种不同入参的单元测试,所以提取
从SqlSessionFactory中获取SqlSession
。
4.4.2 基本类型参数
ApiTest.java
@Test
public void test_queryUserInfoId() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 基本参数User user = userDao.queryUserInfoById(1L);logger.info("测试结果:{}", JSON.toJSONString(user));
}
测试结果
10:06:48.775 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
10:06:48.934 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
10:06:49.805 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1043208434.
10:06:49.815 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
10:06:49.838 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
- 从测试过程中可以在
DefaultParameterHandler#setParameters
中打断点,验证方法参数以及获得到的类型处理器。 - 这里测试可以通过,可以满足基本类型对象的入参信息。
4.4.3 对象类型参数
ApiTest.java
@Test
public void test_queryUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 对象参数User user = userDao.queryUserInfo(new User(1L, "10001"));logger.info("测试结果:{}", JSON.toJSONString(user));
}
测试结果
10:10:05.878 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
10:10:05.878 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userId propertyType:class java.lang.String
10:10:05.947 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfo parameter:{"id":1,"userId":"10001"}
10:10:06.574 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1107024580.
10:10:06.590 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
10:10:06.590 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10001"
10:10:06.590 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
- 从测试结果验证对象参数 User 中包含两个属性时,检查我们的代码处理过程,验证是否可以正确获取到两个类型处理器,分别设置参数的过程。
- 从测试结果看,可以看到测试通过,并打印了相关参数的构建和使用。
五、总结:参数处理器
- 已经把一个 ORM 框架的基本流程串联起来了,包含的分包结构:构建、绑定、映射、反射、执行、类型、事务、数据源等。
- 参数类型的策略化设计,通过策略解耦,模板定义流程,让我们整个参数设置变得更加清晰。
- 还有一些细节的功能点:MapperMethod 中添加方法签名、类型处理器创建和使用时,都使用了 MetaObject 反射器工具类进行处理。