前言
在之前的章节里边,类PreparedStatementHandler我们还没有处理在执行Sql时的参数,目前是硬编码写死存储的,如:ps.setLong(),这里就只能处理long类型的数据因为写死了,我们需要处理下让它支持设置不同的数据类型。
根据要求我们知道本次最重要的就是参数处理器,考虑下需要怎么解析XML中的参数并放到对应容器里,使用时调用参数处理器进行设置对应参数呢?这就是今天的主题。
2. UML类图
本章涉及的内容并不多,新加了7个类,下面图片标灰色以及蓝色和橘黄色都是新增的类,其余的都是根据需求串流程的修改。
2.1 解析生成类型处理器分析
上节我们把XML解析进行解构处理,那么真正解析处理Sql语句以及参数的处理则在SqlSourceBuilder来处理,我们这节以这个为入口在参数处理时考虑加个参数的类型处理器,用来执行SQL语句时最终设置参数处理,这里我们类型处理器只实现了两个类,LongTypeHandler和StringTypeHanler,实际上还会有很多的TypeHandler,本节实现这两个先了解下。
ParameterMappingTokenHandler:SqlSourceBuilder的内部类,主要处理参数映射的,我们在生成ParameterMapping对象时进行修改从类型注册器里找出是否有当前参数类型的值,有则从类型注册器中取,无则通过反射工具类将对象参数解析进行参数类型获取处理。
ParameterMapping:全局变量里添加一个私有的类型处理器的映射,在调用build的方法时进行绑定赋值。
TypeHandler:类型处理器接口,本节添加的类,由于存在于设置参数时使用,只定义了设置参数的方法。
LongTypeHandler:类型处理器实现类,Long类型处理器,只处理Long类型参数值的设置。
StringTypeHandler:类型处理器实现类,String类型处理器,只处理String类型参数值的设置。
TypeHandlerRegistry:类型处理器注册器,定义了启动时直接注册两种(Long,String)类型处理器以及从注册器获取类型处理器方法,判断是否存在此类型处理器方法等。
2.2 使用参数处理器和类型处理器
我们想象一下在哪里需要使用我们处理好的参数映射呢,是不是在Sql执行时,我们把解析好的Sql语句和参数拿过来然后调用JDBC将Sql语句和参数传过去,那么就需要在执行器中的语句处理器下手修改
因为参数执行处理在Mybatis里需要进入预处理语句处理器,所以从这里开始
PreparedStatementHandler:预处理语句处理器实现,在parameterize方法中通过参数处理器进行参数设置,将参数设置到jdbc的PreparedStatement中。
BaseStatementHandler:基础语句处理器,全局变量定义参数处理器,并在构造方法中创建并赋值参数处理器,供PreparedStatementHandler使用。
Configuration:Configuration中修改下添加创建参数处理器的方法供BaseSattementHandler调用
LanguageDriver:语言驱动器,添加了定义了创建参数处理器方法,供Configuration调用
XMLLanguageDriver:语言驱动器实现类,实例化参数处理器DefaultParameterHandler并返回。
ParameterHandler:参数处理器接口,定义了获取参数以及设置参数方法。
DefaultParameterHandler:参数处理器接口实现类,获取参数映射根据参数类型取出对应的类型处理器,并调用对应类型的参数设置,设置完直接执行Sql语句就结束。
本节比较好理解,也比较简单,大家好好学习,好好理解
代码
// doto MapperMethod的参数处理
3.1 类型处理器(TypeHandler)
在Mybatis源码里有type的包,这个包下都是关于类型的处理
package df.middleware.mybatis.type
我们创建类型处理器接口,定义设置参数方法,需要用到的参数有预处理语句(preparedment)以及第一个参数i,参数值paramter,参数类型jdbcType即可。
/**
* @Author df
* @Date 2023/3/13 12:36
* @Version 1.0
* 类型处理器接口
*/
public interface TypeHandler<T> {
/**
* 设置参数
*/
void setParameter(PreparedStatement ps,int i,T paramter,JdbcType jdbcType) throws SQLException;
}
3.1.1 抽象类处理器(BaseTypeHandler)
此类使用了模板设计模式,实现了setParameter方法,定义了设置不为空参数setNonNullParameter的抽象方法。通过抽象出基类便于共有化处理,在此类中也可以判断和处理,目前我们暂未有那么多流程,但写出来分析Mybatis源码时也好知道有这样的结构,如果要扩展也非常方便
/**
* @Author df
* @Date 2023/3/13 12:39
* @Version 1.0
* 类型处理器基类,模板模式
*/
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
setNonNullParameter(ps, i, parameter, jdbcType);
}
protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
3.1.2 LONG类型处理器(LongTypeHandler)
此类继承抽象类处理器,实现setNonNullParameter方法,见名知意,处理Long类型结构的参数,所以使用ps.setLong(),使用过jdbc框架的都理解这里,就不扩展了
/**
* @Author df
* @Date 2023/3/13 12:44
* @Version 1.0
* Long类型处理器
*/
public class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
}
3.1.3 STRING类型处理器(StringTypeHandler)
此类继承抽象类处理器,实现setNonNullParameter方法,见名知意,处理String类型结构的参数,所以使用ps.setString(i,parameter),
/**
* @Author df
* @Date 2023/3/13 12:48
* @Version 1.0
*/
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
}
3.1.4 类型处理器注册器(TypeHandlerRegistry)
类型处理注册器主要是提供了注册类型以及根据类型获取对应的类型处理器,在构造方法中将用到的类型注册全部注册进来,在使用时将参数类型传入进来拿到对应的类型处理器即可。同时可以注意到,无论是对象类型,还是基本类型,都是一个类型处理器。只不过在注册的时候多注册了一个。这种操作方式和我们平常的业务开发中,也是一样的。一种是多注册,另外一种是判断处理
/**
* @Author df
* @Date 2023/3/13 12:50
* @Version 1.0
* 类型处理器注册机
*/
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap();
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLER_MAP = new HashMap<>();
public TypeHandlerRegistry() {
// 注册类型注册机
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
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<>());
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLER_MAP.put(handler.getClass(), handler);
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
return getTypeHandler((Type) type, jdbcType);
}
// 获取当前类的类型处理器
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);
}
return (TypeHandler<T>) handler;
}
// 是否有当前类的类型处理器
public boolean hasTypeHandler(Class<?> javaType) {
return hasTypeHandler(javaType, null);
}
public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
}
}
3.2 SQL源构造器(SqlSource)
SqlSource本身代码没有改变,它的内部类ParameterMappingTokenHandler在参数映射的时候进行了改变,在得到的参数时进行了判断,判断当前参数类是否有对应的类型处理器,如果没有则认同感反射工具类解析对象类型,最后剑门关属性类型,属性名称传递给ParameterMapping进行构建
public ParameterMapping buildParameterMapping(String content) {
// 解析参数映射
Map<String, String> parameterMap = new ParameterExpression(content);
String property = parameterMap.get("property");
Class<?> propertyType;
// 判断是否当前参数类型在类型注册器中存在
if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
// 可直接将参数类型赋值给propertyType,传给ParameterMapping使用
propertyType = parameterType;
} else if (property != null) {
// 如果不是基本类型,如对象需要通过反射类解析属性处理。如:xx.xxxx.User
MetaClass metaClass = MetaClass.forClass(parameterType);
// 解析完毕获取对应的参数类型赋值propertyType
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
} else {
propertyType = Object.class;
}
System.out.println("构建参数映射 property:" + property + "propertyType:" + propertyType);
// 构建ParameterMapping
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
return builder.build();
}
3.2.1 参数映射器(ParameterMapping)
参数映射器改动不多,全局变量里加上了类型处理器typeHandler,并且在内部处理器构建里修改了内容,通过当前的参数映射的java类型获取类型注册器里的类型处理器,并赋值给全局变量typeHandler
private TypeHandler typeHandler;
public static class Builder {
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;
}
}
4. 使用参数类型处理器
4.1 参数处理器(ParameterHandler)
package df.middleware.mybatis.executor.parameter
参数处理器接口,定义了设置参数和获取参数对象方法
/**
* @Author df
* @Date 2023/3/14 12:43
* @Version 1.0
*/
public interface ParameterHandler {
/**
* 获取参数
*/
Object getParameterObject();
void setParameters(PreparedStatement ps) throws SQLException;
}
4.2参数处理器实现类(DefaultParameterHandler)
package df.middleware.mybatis.scripting.defaults;
实现参数类型处理器接口,设置参数时循环处理参数通过ParameterMapping获取的类型处理器可直接调用不同的类型处理器设置参数,完成动态的调用不同的参数类型
/**
* @Author df
* @Date 2023/3/14 12:45
* @Version 1.0
* 默认参数处理器
*/
public class DefaultParameterHandler implements ParameterHandler {
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.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.mappedStatement = mappedStatement;
this.parameterObject = parameterObject;
this.boundSql = boundSql;
this.configuration = mappedStatement.getConfiguration();
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
// 得到参数映射的结果
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (null != parameterMappings) {
for (int i = 0; i < parameterMappings.size(); i++) {
// 循环得到ParameterMapping
ParameterMapping parameterMapping = parameterMappings.get(i);
//得到属性名称
String propertyName = parameterMapping.getProperty();
Object value;
// 判断在哪里获取值
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
JdbcType jdbcType = parameterMapping.getJdbcType();
// 设置参数
System.out.println("根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:" + value);
// 从parameterMapping获取类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 可策略的调用不用的类型处理器
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
4.3 基础语句处理器(BaseStatementHandler)
执行器里的BaseStatementHandler的一些小修改,添加了全局变量参数处理器ParameterHandler,
在构造方法中通过configuration实例化ParameterHandler,并将得到的ParameterHandler赋值给全局变量的一个操作
protected final ParameterHandler parameterHandler;
public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {
// 省略其他章节有的,添加处理parameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
}
4.4 Configuration
configuration中里又依赖了语句驱动器创建参数处理器statement.getLang()就是获取LanguageDriver,职责分明,语句驱动器定义和实现创建参数处理器
public ParameterHandler newParameterHandler(MappedStatement statement, Object paramObject, BoundSql boundSql) {
// 创建参数处理器
ParameterHandler parameterHandler = statement.getLang().createParameterHandler(statement, paramObject, boundSql);
// 插件的一些参数,也是在这里处理,暂时不添加这部分内容 interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
4.5 语言驱动器(LanguageDriver)
添加了createParameterHandler()方法的处理,都蛮简单的
/**
* @Author df
* @Date 2022/12/5 14:16
* @Version 1.0
* 脚本语言驱动
*/
public interface LanguageDriver {
/**
* 创建参数处理器
*/
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
}
4.6 XMLLanguageDriver
xml语言驱动器,实例化DefaultParameterHandler类并返回,最终流转回BaseStatementHandler里供执行语句和参数使用
/**
* @Author df
* @Date 2022/12/5 14:28
* @Version 1.0
* XML语言驱动器
*/
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
}
4.7 预处理语句处理器(PreparedStatementHandler)
在PreparedStatementHandler中可直接调用自己父类的参数处理器即可设置参数
// 参数设置
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
4.8 MapperMethod
再MapperMethod里添加了内部类方法签名类,此类处理封装参数数据,以前是直接将args参数直接传给selectOne方法,调整为转换后的对象传给selectOne方法。
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()]);
final 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<?>[] argsType = method.getParameterTypes();
for (int i = 0; i < argsType.length; i++) {
String paramName = String.valueOf(params.size());
// 不做 Param 的实现,这部分不处理。如果扩展学习,需要添加 Param 注解并做扩展实现。
params.put(i, paramName);
}
return params;
}
}
MapperMethod中的修改,全局变量里添加了MethodSignature属性,在构造函数里赋值,并在执行方法execute中进行参数转换处理就ok了
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
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;
}
3. 测试
user_Mapper.xml里添加一个queryUserInfo的查询Sql。参数类型是一个对象
<select id="queryUserInfo" parameterType="df.middleware.mybatis.po.User" resultType="df.middleware.mybatis.po.User">
SELECT id, userId, userName, userHead
FROM user
where id = #{id} and userId = #{userId}
</select>
单元测试,执行test_queryUserInfo方法
/**
* @Author df
* @Date 2022/12/15 9:00
* @Version 1.0
*/
public class TestApi {
private SqlSession sqlSession;
@Before
public void init() throws IOException {
// 1. 从SqlSessionFactory中获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
sqlSession = sqlSessionFactory.openSession();
}
@org.junit.Test
public void test_queryUserInfoById() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:基本参数
User user = userDao.queryUserInfoById(1L);
System.out.println("测试结果:" + user.getUserName());
}
@org.junit.Test
public void test_queryUserInfo() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:对象参数
User user = userDao.queryUserInfo(new User(1L, "10001"));
System.out.println(user);
System.out.println(user.getUserName());
}
}
执行结果,从打印来看我们能够构建long类型和String类型的处理器,最终根据类型处理器设置对应的参数类型的值,并最终执行成功了Sql语句