便宜的網(wǎng)站設(shè)計(jì)企業(yè)查詢官網(wǎng)入口
文章目錄
- 一、目標(biāo):參數(shù)處理器
- 二、設(shè)計(jì):參數(shù)處理器
- 三、實(shí)現(xiàn):參數(shù)處理器
- 3.1 工程結(jié)構(gòu)
- 3.2 參數(shù)處理器關(guān)系圖
- 3.3 入?yún)?shù)校準(zhǔn)
- 3.4 參數(shù)策略處理器
- 3.4.1 JDBC枚舉類型修改
- 3.4.2 類型處理器接口
- 3.4.3 模板模式:類型處理器抽象基類
- 3.4.4 類型處理器具體的子類實(shí)現(xiàn)
- 3.4.5 類型處理器注冊機(jī)
- 3.5 參數(shù)構(gòu)建
- 3.5.1 參數(shù)映射類的修改
- 3.5.2 SQL源碼構(gòu)建器
- 3.6 參數(shù)使用
- 3.6.1 參數(shù)處理器接口
- 3.6.2 默認(rèn)參數(shù)處理器
- 3.7 參數(shù)處理器的使用
- 3.7.1 腳本語言驅(qū)動
- 3.7.2 XML語言驅(qū)動器
- 3.7.3 映射器語句類修改,添加腳本語言驅(qū)動
- 3.7.4 修改配置類
- 3.7.5 語句處理器抽象基類
- 3.7.6 預(yù)處理語句處理器
- 3.7.7 默認(rèn)sqlSession實(shí)現(xiàn)類添加日志描述
- 四、測試:參數(shù)處理器
- 4.1 修改 DAO 接口
- 4.2 修改 User 實(shí)體類
- 4.3 配置Mapper文件
- 4.4 單元測試
- 4.4.1 提取初始化SqlSession
- 4.4.2 基本類型參數(shù)
- 4.4.3 對象類型參數(shù)
- 五、總結(jié):參數(shù)處理器
一、目標(biāo):參數(shù)處理器
💡 結(jié)合參數(shù)的提取,對執(zhí)行的 SQL 進(jìn)行參數(shù)的自動化設(shè)置,而不是像我們之前那樣把參數(shù)寫成固定的。
- 在流程上,通過
DefaultSqlSession#selectOne
方法調(diào)用執(zhí)行器,并通過預(yù)處理語句處理器PreparedStatementHandler
執(zhí)行參數(shù)設(shè)置和結(jié)果查詢。 - 這個(gè)流程中我們處理的參數(shù)信息,也就是每個(gè) SQL 執(zhí)行時(shí),那些
?號
需要被替換的地方,目前是通過硬編碼的方式進(jìn)行處理的。 - 使用策略模式,處理硬編碼為自動化類型設(shè)置。
二、設(shè)計(jì):參數(shù)處理器
💡 在 SQL 拆解出參數(shù)類型之后,怎么根據(jù)不同的參數(shù)進(jìn)行不同的類型設(shè)置
- 在自動化解析 XML 中 SQL 拆分出所有的參數(shù)類型后,則應(yīng)該根據(jù)不同的參數(shù)進(jìn)行不同的類型設(shè)置
Long
調(diào)用ps.setLong
,String
調(diào)用ps.setString
- 這里使用 策略模式,封裝進(jìn)去類型處理器(也就是實(shí)現(xiàn) TypeHandler 接口的過程)。
- 其實(shí)關(guān)于參數(shù)的處理,因?yàn)橛泻芏嗟念愋?
Long\String\Object\...
),所以這里最重要的體現(xiàn)則是 策略模式 的使用。- 包括:構(gòu)建參數(shù)時(shí)根據(jù)類型,選擇對應(yīng)的策略類型處理器,填充到參數(shù)映射集合中。
- 另一方面:參數(shù)的使用,也就是在執(zhí)行
DefaultSqlSession#selectOne
的鏈路中。- 包括:參數(shù)的設(shè)置,按照參數(shù)不同類型,獲取出對應(yīng)的處理器,以及入?yún)ⅰ?/li>
- 注意:由于入?yún)⒅悼赡苁且粋€(gè)對象中的屬性,所以我們使用反射工具類 MetaObject 進(jìn)行值的獲取,避免由于動態(tài)的對象,沒法硬編碼獲取屬性值。
三、實(shí)現(xiàn):參數(shù)處理器
3.1 工程結(jié)構(gòu)
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 參數(shù)處理器關(guān)系圖
- 策略模式,核心處理主要分為三塊:類型處理、參數(shù)設(shè)置、參數(shù)使用。
- 定義
TypeHandler
類型處理器策略接口,實(shí)現(xiàn)不同的處理策略,包括:Long、String、Integer
等。 - 類型策略處理器實(shí)現(xiàn)完成后,需要注冊到處理器注冊機(jī)中,后續(xù)其他模塊參數(shù)的設(shè)置的使用都是從 Configuration 中獲取到 TypeHandlerRegistry 進(jìn)行使用。
- 有了策略處理器之后,在進(jìn)行操作解析 SQL 時(shí),就可以按照不同的類型把對應(yīng)的策略處理器設(shè)置到
BoundSql#parameterMappings
參數(shù)里。
- 定義
3.3 入?yún)?shù)校準(zhǔn)
💡 針對參數(shù)的傳遞?
- 這里的參數(shù)傳遞后,需要獲取第0個(gè)參數(shù),而且是硬編碼固定。
- 這是為什么呢?這個(gè)第0個(gè)參數(shù)是哪來的,我們接口調(diào)用的方法,參數(shù)不是一個(gè)嗎?就像
User queryUserInfoById(Long id)
.
- 這是為什么呢?這個(gè)第0個(gè)參數(shù)是哪來的,我們接口調(diào)用的方法,參數(shù)不是一個(gè)嗎?就像
- 其實(shí)這個(gè)參數(shù)來自映射器代理類
MapperProxy#invoke
中,因?yàn)?invoke
反射調(diào)用的方法,入?yún)⒅惺?Object[] args
,所以這個(gè)參數(shù)被傳遞到后續(xù)的參數(shù)設(shè)置中。而我們的 DAO 測試類是一個(gè)已知的固定參數(shù),所以后面硬編碼獲取了第0個(gè)參數(shù)。- JDK 反射調(diào)用方法操作固定方法入?yún)ⅰ?/li>
- 現(xiàn)在我們需要根據(jù)方法的信息,給方法做簽名操作,以便于轉(zhuǎn)換入?yún)⑿畔榉椒ǖ男畔ⅰ1热?#xff1a;數(shù)組轉(zhuǎn)換為對應(yīng)的對象。
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) {// 如果沒參數(shù)return null;} else if (paramCount == 1) {return args[params.keySet().iterator().next().intValue()];} else {// 否則,返回一個(gè)ParamMap, 修改參數(shù)名,參數(shù)名就是其位置final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {// 1.先加一個(gè)#{0},#{1},#{2}...參數(shù)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.再加一個(gè)#{param1},#{param2}...參數(shù)你可以傳遞多個(gè)參數(shù)給一個(gè)映射器方法。默認(rèn)情況下它們將會以它們在參數(shù)列表中的位置來命名,比如:#{param1},#{param2}等如果你想改變參數(shù)的名稱(只在多參數(shù)情況下),那么你可以在參數(shù)上使用@Param("paramName")注解*/param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}private SortedMap<Integer, String> getParams(Method method) {// 用一個(gè)TreeMap,這樣就保證還是按參數(shù)的先后順序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 的實(shí)現(xiàn),這部分不處理。params.put(i, paramName);}return params;}/*** 參數(shù)map,靜態(tài)內(nèi)部類,更嚴(yán)格的get方法,如果沒有相應(yīng)的key,報(bào)錯*/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
將原來的直接將參數(shù) args 傳遞給SqlSession#selectOne
方法,調(diào)整為轉(zhuǎn)換后再傳遞對象。 - 這里的轉(zhuǎn)換操作就是來自于
Method#getParameterTypes
對參數(shù)的獲取和處理,于 args 進(jìn)行對比。- 如果是單個(gè)參數(shù),則直接返回參數(shù) Tree 樹結(jié)構(gòu)下的對應(yīng)節(jié)點(diǎn)值。
- 非單個(gè)類型,則需要進(jìn)行循環(huán)處理,這樣轉(zhuǎn)換后的參數(shù)才能被直接使用。
3.4 參數(shù)策略處理器
- Mybatis 的源碼包中,type 包下所提供的就是一套參數(shù)的處理策略集合。
- 它通過定義類型處理器接口、由抽象模板實(shí)現(xiàn)并定義標(biāo)準(zhǔn)流程,定義提取抽象方法交予給子類實(shí)現(xiàn),這些之類就是各個(gè)類型處理器的具體實(shí)現(xiàn)。
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> {/*** 設(shè)置參數(shù)** @param ps 預(yù)處理語言* @param i 次數(shù)* @param parameter 參數(shù)對象* @param jdbcType JDBC類型* @throws SQLException SQL異常*/void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
- 首先定義一個(gè)類型處理器的接口。
- 這里設(shè)置參數(shù)也是一樣,所有不同類型的參數(shù),都可以被提取出來這些標(biāo)準(zhǔn)的參數(shù)字段和異常,后續(xù)的子類按照這個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn)即可。
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 {// 定義抽象方法,由子類實(shí)現(xiàn)不同類型的屬性設(shè)置setNonNullParameter(ps, i, parameter, jdbcType);}/*** 屬性設(shè)置:抽象方法,由子類實(shí)現(xiàn)** @param ps 預(yù)處理語言* @param i 次數(shù)* @param parameter 參數(shù)對象* @param jdbcType JDBC類型* @throws SQLException SQL異常*/protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
- 通過抽象基類的流程模板定義,便于一些參數(shù)的判斷和處理。
3.4.4 類型處理器具體的子類實(shí)現(xiàn)
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);}
}
- 這里的接口實(shí)現(xiàn)暫時(shí)實(shí)現(xiàn)了3個(gè),分別是
IntegerTypeHandler、LongTypeHandler、StringTypeHandler
。 - 在 Mybatis 中源碼還有30+個(gè)其他類型,可以參照源碼閱讀。
3.4.5 類型處理器注冊機(jī)
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: 類型處理器注冊機(jī)*/
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;}
}
- 這里在構(gòu)造函數(shù)中,新增加了
LongTypeHandler、IntegerTypeHandler、StringTypeHandler
三種類型的注冊機(jī)。 - 同時(shí)注意到,無論是對象類型,還是基本類型,都是一個(gè)類型處理器。只不過在注冊的時(shí)候多注冊了一個(gè)。
- 這種操作方式和我們平常的業(yè)務(wù)開發(fā)中,也是一樣的。一種是多注冊,另外一種是判斷處理。
3.5 參數(shù)構(gòu)建
- 需要對 SqlSourceBuilder 源碼構(gòu)建器中,創(chuàng)建參數(shù)映射 ParameterMapping 需要添加參數(shù)處理器的內(nèi)容。
- 因?yàn)橹挥羞@樣才能方便的從參數(shù)映射中獲取到對應(yīng)類型的處理器進(jìn)行使用。
- 需要完善 ParameterMappping 添加 TypeHandler 屬性信息。
- 以及在
ParameterMappingTokenHandler#buildParameterMapping
處理參數(shù)映射時(shí),構(gòu)建出參數(shù)的映射。
- 以及在
3.5.1 參數(shù)映射類的修改
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: 參數(shù)映射 #{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源碼構(gòu)建器
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源碼構(gòu)建器*/
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);// 返回靜態(tài)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 "?";}/*** 構(gòu)建參數(shù)映射** @param content 參數(shù)* @return 參數(shù)映射*/private ParameterMapping buildParameterMapping(String content) {// 先解析參數(shù)映射,就是轉(zhuǎn)化成一個(gè) 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("構(gòu)建參數(shù)映射 property:{} propertyType:{}", property, propertyType);ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);return builder.build();}}
}
- 這部分就是對參數(shù)的細(xì)化處理,構(gòu)建出參數(shù)的映射關(guān)系。
- 首先是 if 判斷對應(yīng)的參數(shù)類型是否在 TypeHandlerRegistry 注冊器中。
- 如果不在則拆解對象,按屬性進(jìn)行獲取 propertyType 的操作。
- 這一塊也用到了 MetaClass 反射工具類的使用。
3.6 參數(shù)使用
- 參數(shù)構(gòu)建完成后,就可以在
DefaultSqlSession#selectOne
調(diào)用時(shí)設(shè)置參數(shù)使用了。- 鏈路:
Executor#query -> SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters
。 - 到
ParameterHandler#setParameters
就可以看到了根據(jù)參數(shù)的不同處理器循環(huán)設(shè)置參數(shù)。
- 鏈路:
3.6.1 參數(shù)處理器接口
ParameterHandler.java
package com.lino.mybatis.executor.parameter;import java.sql.PreparedStatement;
import java.sql.SQLException;/*** @description: 參數(shù)處理器*/
public interface ParameterHandler {/*** 獲取參數(shù)** @return 參數(shù)*/Object getParameterObject();/*** 設(shè)置參數(shù)** @param ps 預(yù)處理語句對象* @throws SQLException sql異常*/void setParameters(PreparedStatement ps) throws SQLException;
}
3.6.2 默認(rèn)參數(shù)處理器
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: 默認(rèn)參數(shù)處理器*/
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 反射取得值設(shè)置進(jìn)去MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}JdbcType jdbcType = parameterMapping.getJdbcType();// 設(shè)置參數(shù)logger.info("根據(jù)每個(gè)ParameterMapping中的TypeHandler設(shè)置對應(yīng)的參數(shù)信息 value:{}", JSON.toJSONString(value));TypeHandler typeHandler = parameterMapping.getTypeHandler();typeHandler.setParameter(ps, i + 1, value, jdbcType);}}}
}
- 每一個(gè)循環(huán)的參數(shù)設(shè)置,都是從 BoundSql 中獲取 ParameterMapping 集合進(jìn)行循環(huán)操作,而這個(gè)集合參數(shù)就是我們前面
ParameterMappingTokenHandler#buildParameterMapping
構(gòu)建參數(shù)映射時(shí)處理的。 - 設(shè)置參數(shù)時(shí)根據(jù)參數(shù)的 parameterObject 入?yún)⑿畔?#xff0c;判斷是否基本類型,如果不是則從對象中進(jìn)行拆解獲取(也就是一個(gè)對象A中包括對象B),處理完成后就可以準(zhǔn)確拿到對應(yīng)的入?yún)⒅怠?
- 入?yún)⒅翟?MapperMethod 中已經(jīng)處理了一遍 方法簽名。
- 基本信息獲取完成后,則根據(jù)參數(shù)類型獲取到對應(yīng)的 TypeHandler 類型處理器,也就是找到
IntegerTypeHandler、LongTypeHandler、StringTypeHandler
等,確定找到以后,則可以進(jìn)行對應(yīng)的參數(shù)設(shè)置。typeHandler.setParameter(ps, i + 1, value, jdbcType)
通過這樣的方式把我們之前硬編碼的操作進(jìn)行解耦。
3.7 參數(shù)處理器的使用
3.7.1 腳本語言驅(qū)動
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: 腳本語言驅(qū)動*/
public interface LanguageDriver {/*** 創(chuàng)建SQL源** @param configuration 配置項(xiàng)* @param script 元素* @param parameterType 參數(shù)類型* @return SqlSource SQL源碼*/SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType);/*** 創(chuàng)建參數(shù)處理器** @param mappedStatement 映射器語句類* @param parameterObject 參數(shù)對象* @param boundSql sql語句* @return ParameterHandler 參數(shù)處理器*/ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
}
- 添加創(chuàng)建參數(shù)處理器接口
3.7.2 XML語言驅(qū)動器
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 語言驅(qū)動器*/
public class XMLLanguageDriver implements LanguageDriver {@Overridepublic SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {// 用XML腳本構(gòu)建器解析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 映射器語句類修改,添加腳本語言驅(qū)動
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: 配置項(xiàng)*/
public class Configuration {/*** 環(huán)境*/protected Environment environment;/*** 映射注冊機(jī)*/protected MapperRegistry mapperRegistry = new MapperRegistry(this);/*** 映射的語句,存在Map里*/protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);/*** 類型別名注冊機(jī)*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();/*** 腳本語言注冊器*/protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();/*** 類型處理器注冊機(jī)*/protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();/*** 對象工廠*/protected ObjectFactory objectFactory = new DefaultObjectFactory();/*** 對象包裝工廠*/protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();/*** 準(zhǔn)備資源列表*/protected final Set<String> loadedResources = new HashSet<>();/*** 數(shù)據(jù)庫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;}/*** 生產(chǎn)執(zhí)行器** @param transaction 事務(wù)* @return 執(zhí)行器*/public Executor newExecutor(Transaction transaction) {return new SimpleExecutor(this, transaction);}/*** 創(chuàng)建語句處理器** @param executor 執(zhí)行器* @param mappedStatement 映射器語句類* @param parameter 參數(shù)* @param resultHandler 結(jié)果處理器* @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);}/*** 創(chuàng)建結(jié)果集處理器** @param executor 執(zhí)行器* @param mappedStatement 映射器語句類* @param boundSql SQL語句* @return ResultSetHandler 結(jié)果集處理器*/public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, BoundSql boundSql) {return new DefaultResultSetHandler(executor, mappedStatement, boundSql);}/*** 創(chuàng)建元對象** @param object 原對象* @return 元對象*/public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory);}/*** 創(chuàng)建類型處理器注冊機(jī)** @return TypeHandlerRegistry 類型處理器注冊機(jī)*/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);}/*** 獲取腳本語言注冊機(jī)** @return languageRegistry 腳本語言注冊機(jī)*/public LanguageDriverRegistry getLanguageRegistry() {return languageRegistry;}/*** 獲取參數(shù)處理器** @param mappedStatement 映射器語言類型* @param parameterObject 參數(shù)對象* @param boundSql SQL語句* @return ParameterHandler參數(shù)處理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 創(chuàng)建參數(shù)處理器ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 獲取默認(rèn)腳本語言驅(qū)動** @return 腳本語言驅(qū)動*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}
}
- 添加獲取默認(rèn)腳本語言驅(qū)動
getDefaultScriptingLanguageInstance
- 添加獲取參數(shù)處理器
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 {// 實(shí)例化 Statementstatement = instantiateStatement(connection);// 參數(shù)設(shè)置,可以被抽取,提供配置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 參數(shù)處理器
3.7.6 預(yù)處理語句處理器
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: 預(yù)處理語句處理器(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 默認(rèn)sqlSession實(shí)現(xiàn)類添加日志描述
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: 默認(rèn)sqlSession實(shí)現(xiàn)類*/
public class DefaultSqlSession implements SqlSession {private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class);...@Overridepublic <T> T selectOne(String statement, Object parameter) {logger.info("執(zhí)行查詢 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);}...
}
四、測試:參數(shù)處理器
4.1 修改 DAO 接口
IUserDao.java
package com.lino.mybatis.test.dao;import com.lino.mybatis.test.po.User;/*** @Description: 用戶持久層*/
public interface IUserDao {/*** 根據(jù)ID查詢用戶信息** @param uId ID* @return User 用戶*/User queryUserInfoById(Long uId);/*** 根據(jù)用戶對象查詢用戶信息* @param user 用戶* @return User 用戶*/User queryUserInfo(User user);
}
4.2 修改 User 實(shí)體類
User.java
package com.lino.mybatis.test.po;import java.util.Date;/*** @description: 用戶實(shí)例類*/
public class User {private Long id;/*** 用戶ID*/private String userId;/*** 頭像*/private String userHead;/*** 用戶名稱*/private String userName;/*** 創(chuàng)建時(shí)間*/private Date createTime;/*** 更新時(shí)間*/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();
}
- 因?yàn)榻酉聛硇枰?yàn)證兩種不同入?yún)⒌膯卧獪y試,所以提取
從SqlSessionFactory中獲取SqlSession
。
4.4.2 基本類型參數(shù)
ApiTest.java
@Test
public void test_queryUserInfoId() {// 1.獲取映射器對象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.測試驗(yàn)證: 基本參數(shù)User user = userDao.queryUserInfoById(1L);logger.info("測試結(jié)果:{}", JSON.toJSONString(user));
}
測試結(jié)果
10:06:48.775 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 構(gòu)建參數(shù)映射 property:id propertyType:class java.lang.Long
10:06:48.934 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 執(zhí)行查詢 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 - 根據(jù)每個(gè)ParameterMapping中的TypeHandler設(shè)置對應(yīng)的參數(shù)信息 value:1
10:06:49.838 [main] INFO com.lino.mybatis.test.ApiTest - 測試結(jié)果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小靈哥"}
- 從測試過程中可以在
DefaultParameterHandler#setParameters
中打斷點(diǎn),驗(yàn)證方法參數(shù)以及獲得到的類型處理器。 - 這里測試可以通過,可以滿足基本類型對象的入?yún)⑿畔ⅰ?/li>
4.4.3 對象類型參數(shù)
ApiTest.java
@Test
public void test_queryUserInfo() {// 1.獲取映射器對象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.測試驗(yàn)證: 對象參數(shù)User user = userDao.queryUserInfo(new User(1L, "10001"));logger.info("測試結(jié)果:{}", JSON.toJSONString(user));
}
測試結(jié)果
10:10:05.878 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 構(gòu)建參數(shù)映射 property:id propertyType:class java.lang.Long
10:10:05.878 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 構(gòu)建參數(shù)映射 property:userId propertyType:class java.lang.String
10:10:05.947 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 執(zhí)行查詢 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 - 根據(jù)每個(gè)ParameterMapping中的TypeHandler設(shè)置對應(yīng)的參數(shù)信息 value:1
10:10:06.590 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根據(jù)每個(gè)ParameterMapping中的TypeHandler設(shè)置對應(yīng)的參數(shù)信息 value:"10001"
10:10:06.590 [main] INFO com.lino.mybatis.test.ApiTest - 測試結(jié)果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小靈哥"}
- 從測試結(jié)果驗(yàn)證對象參數(shù) User 中包含兩個(gè)屬性時(shí),檢查我們的代碼處理過程,驗(yàn)證是否可以正確獲取到兩個(gè)類型處理器,分別設(shè)置參數(shù)的過程。
- 從測試結(jié)果看,可以看到測試通過,并打印了相關(guān)參數(shù)的構(gòu)建和使用。
五、總結(jié):參數(shù)處理器
- 已經(jīng)把一個(gè) ORM 框架的基本流程串聯(lián)起來了,包含的分包結(jié)構(gòu):構(gòu)建、綁定、映射、反射、執(zhí)行、類型、事務(wù)、數(shù)據(jù)源等。
- 參數(shù)類型的策略化設(shè)計(jì),通過策略解耦,模板定義流程,讓我們整個(gè)參數(shù)設(shè)置變得更加清晰。
- 還有一些細(xì)節(jié)的功能點(diǎn):MapperMethod 中添加方法簽名、類型處理器創(chuàng)建和使用時(shí),都使用了 MetaObject 反射器工具類進(jìn)行處理。