手写Mybatis:第10章-使用策略模式,调用参数处理器

news2024/10/5 10:06:20

文章目录

  • 一、目标:参数处理器
  • 二、设计:参数处理器
  • 三、实现:参数处理器
    • 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 拆解出参数类型之后,怎么根据不同的参数进行不同的类型设置

  • 在自动化解析 XMLSQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置
    • Long 调用 ps.setLongString 调用 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).

在这里插入图片描述

  • 其实这个参数来自映射器代理类 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 compatibility
                    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<?>[] 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;

            @Override
            public 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;
    }

    @Override
    public 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> {
    @Override
    protected 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> {
    @Override
    protected 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> {
    @Override
    protected 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 here
        return (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);
        // 返回静态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;
            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;
    }

    @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 = 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 {

    @Override
    public SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {
        // 用XML脚本构建器解析
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }

    @Override
    public 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);
    }

    @Override
    public Statement prepare(Connection connection) {
        Statement statement = null;
        try {
            // 实例化 Statement
            statement = 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);
    }

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        return connection.prepareStatement(sql);
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }

    @Override
    public <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);

	...

    @Override
    public <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, userHead
        FROM user
        WHERE id = #{id}
    </select>

    <select id="queryUserInfo" parameterType="com.lino.mybatis.test.po.User" resultType="com.lino.mybatis.test.po.User">
        SELECT id, userId, userName, userHead
        FROM user
        where 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中获取SqlSession
    SqlSessionFactory 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 反射器工具类进行处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/971830.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

drone的简单使用

&#xff08;一&#xff09;简介 Drone 是一个基于Docker容器技术的可扩展的持续集成引擎&#xff0c;用于自动化测试、构建、发布。每个构建都在一个临时的Docker容器中执行&#xff0c;使开发人员能够完全控制其构建环境并保证隔离。开发者只需在项目中包含 .drone.yml文件&…

Java“牵手”唯品会商品列表数据,关键词搜索唯品会商品数据接口,唯品会API申请指南

唯品会商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取唯品会商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问唯品会商城的网页来获取商品详情信息。以下是两种常用方法的介…

《智能网联汽车自动驾驶功能测试规程》

一、 编制背景 2018 年4 月12 日&#xff0c;工业和信息化部、公安部、交通运输部联合发布《智能网联汽车道路测试管理规范(试行)》&#xff08;以下简称《管理规范》&#xff09;&#xff0c;对智能网联汽车道路测试申请、审核、管理以及测试主体、测试驾驶人和测试车辆要求等…

webpack5 (三)

webpack 高级配置 其实就是对 webpack 进行优化&#xff0c;让代码在编译/运行时性能更好 1. 提升开发体验 2. 提升打包构建速度 3. 减少代码体积 4. 优化代码运行性能 一、提升开发体验 sourcemap 在编译打包后所有的 css 和 js 都合并为了一个文件&#xff0c;并多了很多…

管理类联考——数学——汇总篇——知识点突破——数据分析——计数原理

角度——⛲️ 一、考点讲解 分类计数原理&#xff08;加法原理&#xff09; (1&#xff09;定义 如果完成一件事有n类办法&#xff0c;只要选择其中一类办法中的任何一种方法&#xff0c;就可以完成这件事。若第一类办法中有 m 1 m_1 m1​种不同的方法&#xff0c;第二类办法中…

SpringCloud(35):Nacos 服务发现快速入门

本小节,我们将演示如何使用Spring Cloud Alibaba Nacos Discovery为Spring cloud 应用程序与 Nacos 的无缝集成。 通过一些原生的spring cloud注解,我们可以快速来实现Spring cloud微服务的服务发现机制,并使用Nacos Server作为服务发现中心,统一管理所有微服务。 1 Spring…

uniapp集成windicss的流程

一、背景介绍 Windicss是一个基于Tailwind CSS 灵感的库,它更快、更兼容,使用 TypeScript 构建。Windicss的目标是为了解决与Tailwind CSS 类似的问题,提供一个可以快速上手开发的组件库,让开发者不再需要繁琐地编写 CSS 样式。Windicss包含了几乎所有的 CSS 样式,因此开发…

MongoDB常用的比较符号和一些功能符号

比较符号 results collection.find({age: {$gt: 20}})功能符号 results collection.find({name: {$regex: ^M.*}})

联合教育部高等学校科学研究发展中心,阿依瓦科技创新教育专项正式发布!

7 月 24 日&#xff0c;教育部科技发展中心官网发布了《中国高校产学研创新基金&#xff0d;阿依瓦科技创新教育专项申请指南》。 针对高校在人工智能、智能制造、智慧校园、大数据等领域科研和教研的创新研究&#xff0c;教育部高等学校科学研究发展中心与阿依瓦(北京)技术有…

RK3399平台开发系列讲解(内核调试篇)IO 数据工具:iostat和iotop

🚀返回专栏总目录 文章目录 一、iostat 命令二、/proc/diskstats 文件三、iotop 命令沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 在 Linux 系统上,iostat 和 iotop 这两个 IO 数据工具非常常用。它们都是性能分析领域中不可缺少的工具性软件。 一、iostat 命令…

linux和docker下mysql安装

目录 一、linux下mysql的安装 1.进入到/etc/yum.repos.d 2.编辑vim mysql-community.repo 3.编辑以下内容 4.保存退出&#xff0c;更新缓存yum makecache 5.下载mysql 6.启动并查看mysql状态 7.查找mysql密码 8.登陆mysql 9.密码修改参考MySQL密码修改 二、docker安…

solidity开发环境配置,vscode搭配remix

#学习笔记 初学solidity&#xff0c;使用remix非常方便&#xff0c;因为需要的环境都配置好了&#xff0c;打开网站就可以使用。 不过在编写代码方面&#xff0c;使用vscode更方便&#xff0c;而vscode本身并不能像remix那样部署合约&#xff0c;它还需要安装插件。 点击红色箭…

springboot整合mybatis实现增删改查(xml)--项目阶段1

目录 一、前言 二、创建项目 创建MySQL数据库和表 创建springboot项目 本文总体代码结构图预览 三、编写代码 &#xff08;一&#xff09;新建实体层属性类 &#xff08;二&#xff09;新建数据层mapper接口 &#xff08;三&#xff09;新建mapper的映射SQL&#xff08…

QProcess 调用 ffmpeg来处理音频

项目场景&#xff1a; 在文章 qt 实现音视频的分贝检测系统中&#xff0c;实现的是边播放变解析音频数据来统计音频的分贝大小&#xff0c;并不满足实际项目的需求&#xff0c;有的视频声音正常&#xff0c;有的视频声音就偏低&#xff0c;即使放到最大音量声音也是比较小&…

Apache实现weblogic集群配置

安装apache&#xff0c;安装相对稳定的版本。如果安装后测试能否正常启动&#xff0c;可以通过访问http://localhost/进行测试。安装Weblogic&#xff0c;参见文档将bea安装目录 weblogic81/server/bin 下的 mod_wl_20.so 文件copy到 apache安装目录下Apache2/modules/目录下A…

90、00后严选出的数据可视化工具:奥威BI工具

90、00后主打一个巧用工具&#xff0c;绝不低效率上班&#xff0c;因此当擅长大数据智能可视化分析的BI数据可视化工具出现后&#xff0c;自然而然地就成了90、00后职场人常用的数据可视化工具。 奥威BI工具三大特点&#xff0c;让职场人眼前一亮&#xff01; 1、零编程&…

【STM32】学习笔记-时间戳RTC

Unix时间戳 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同&#xff0c;不同时区通…

Arrays.asList 和 null 类型

一、Arrays.asList 类型简析 Arrays.asList() 返回的List 是它的内部类&#xff0c;不能使用 retainAll() 取交集&#xff0c;导致元素的删除&#xff0c;会报错。 List<String> list Arrays.asList(value.split(",")); 替换为> List<String> list…

物联网应用中蓝牙模块怎么选?_蓝牙模块厂家

在蓝牙模块选型前期&#xff0c;一定要了解应用场景以及需要实现的功能&#xff08;应用框图&#xff09;&#xff0c;以及功能实现过程中所能提供调用的接口&#xff08;主从设备&#xff0c;功能&#xff09;&#xff0c;考虑模块供电&#xff0c;尺寸&#xff0c;接收灵敏度…

leetcode每日一练-第53题-最大子数组和

一、思路 动态规划 二、解题方法 使用了两个变量 maxSum 和 currentSum 来分别记录全局的最大和和当前连续子数组的和。遍历数组时&#xff0c;我们不断更新 currentSum&#xff0c;并比较是否需要更新 maxSum。最后&#xff0c;maxSum 就是最大的连续子数组和。 三、code …