日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
使用策略模式,調(diào)用參數(shù)處理器

一、前言

你這代碼寫的,咋這么軸呢!

創(chuàng)新互聯(lián)公司專注于欽州企業(yè)網(wǎng)站建設,響應式網(wǎng)站開發(fā),商城網(wǎng)站制作。欽州網(wǎng)站建設公司,為欽州等地區(qū)提供建站服務。全流程按需網(wǎng)站建設,專業(yè)設計,全程項目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務

說到軸,讓我想起初中上學時老師說的話:“你那腦瓜子,咋跟手燜子似的!”

東北話手燜子就是那種冬天戴的大棉手套,棉手套里的棉花都被壓的又沉又硬的了,所以來比喻腦瓜子笨。

而寫軸代碼的大部分都是剛畢業(yè)沒多久,或者剛開始工作的碼農(nóng),畢竟經(jīng)驗不足經(jīng)歷不多,寫出一些不太好維護的代碼也情有可原。而那些絕對多數(shù)鍛煉出來的老碼農(nóng),其實代碼的穩(wěn)定程度、設計經(jīng)驗、縝密邏輯,都是相對來說要好很多的。當然一部分老碼農(nóng),只是老了而已,代碼還是那個代碼!

所以企業(yè)招聘些年輕人,需要年輕的思想。但沒必要嚯嚯只是頭發(fā)沒多少的老碼農(nóng),否則誰來給你平穩(wěn)落地你那些天馬行空的想法呢!難道體驗、穩(wěn)定、流暢,不應該是更值得追求的,非得喜歡全是愣頭青似的代碼,寫出幾百個bug,造成大量資損和客訴,讓老板覺得很爽?

二、目標

上一章節(jié),小傅哥帶著大家細化的 XML 語句構建器,解耦在解析 XML 中的所需要處理的 Mapper 信息,包括;SQL、入?yún)?、出參、類型,并對這些信息進行記錄到 ParameterMapping 參數(shù)映射處理類中。那么這個一章節(jié)我們將結合這部分參數(shù)的提取,對執(zhí)行的 SQL 進行參數(shù)的自動化設置,而不是像我們之前那樣把參數(shù)寫成固定的,如圖 10-1 所示:

圖 10-1 硬編碼參數(shù)設置

在流程上,通過 DefaultSqlSession#selectOne 方法調(diào)用執(zhí)行器,并通過預處理語句處理器 PreparedStatementHandler 執(zhí)行參數(shù)設置和結果查詢。

那么這個流程中我們所處理的參數(shù)信息,也就是每個 SQL 執(zhí)行時,那些?號需要被替換的地方,目前是通過硬編碼的方式進行處理的。而這就是本章節(jié)需要解決的問題,如果只是硬編碼完成參數(shù)設置,那么對于所有那些不同類型的參數(shù)就沒法進行操作了。

所以本章節(jié)需要結合結合上一章節(jié)所完成的語句構建器對 SQL 參數(shù)信息的拆解,本章將會按照這些參數(shù)的解析,處理這里硬編碼為自動化類型設置。針對不同類型的參數(shù)設置,這部分使用了什么設計模式呢?

三、設計

這里可以思考下,參數(shù)的處理也就是通常我們使用 JDBC 直接操作數(shù)據(jù)庫時,所使用 ps.setXxx(i, parameter);

設置的各類參數(shù)。那么在自動化解析 XML 中 SQL 拆分出所有的參數(shù)類型后,則應該根據(jù)不同的參數(shù)進行不同的類型設置,也就;Long 調(diào)用 ps.setLong、String 調(diào)用 ps.setString 所以這里需要使用策略模式,在解析 SQL 時按照不同的執(zhí)行策略,封裝進去類型處理器(也就是是實現(xiàn) TypeHandler接口的過程)。整體設計如圖 10-2 所示:

圖 10-2 策略模式處理參數(shù)處理器

  • 其實關于參數(shù)的處理,因為有很多的類型(Long\String\Object\...),所以這里最重要的體現(xiàn)則是策略模式的使用。
  • 這里包括了構建參數(shù)時根據(jù)類型,選擇對應的策略類型處理器,填充到參數(shù)映射集合中。另外一方面是參數(shù)的使用,也就是在執(zhí)行DefaultSqlSession#selectOne 的鏈路中,包括了參數(shù)的設置,按照參數(shù)的不同類型,獲取出對應的處理器,以及入?yún)⒅?。注意:由于入?yún)⒅悼赡苁且粋€對象中的屬性,所以這里我們用到了前面章節(jié)實現(xiàn)的反射類工具MetaObject 進行值的獲取,避免由于動態(tài)的對象,沒法硬編碼獲取屬性值。

四、實現(xiàn)

1. 工程結構

mybatis-step-09
└── src
├── main
│ └── java
│ └── cn.bugstack.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
│ ├── executor
│ │ ├── resultset
│ │ │ └── ParameterHandler.java
│ │ ├── resultset
│ │ │ ├── DefaultResultSetHandler.java
│ │ │ └── ResultSetHandler.java
│ │ ├── statement
│ │ │ ├── BaseStatementHandler.java
│ │ │ ├── PreparedStatementHandler.java
│ │ │ ├── SimpleStatementHandler.java
│ │ │ └── StatementHandler.java
│ │ ├── BaseExecutor.java
│ │ ├── Executor.java
│ │ └── SimpleExecutor.java
│ ├── io
│ ├── mapping
│ │ ├── BoundSql.java
│ │ ├── Environment.java
│ │ ├── MappedStatement.java
│ │ ├── ParameterMapping.java
│ │ ├── SqlCommandType.java
│ │ └── SqlSource.java
│ ├── parsing
│ ├── reflection
│ ├── scripting
│ │ ├── defaults
│ │ │ └── DefaultParameterHandler.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
│ └── type
│ ├── BaseTypeHandler.java
│ ├── JdbcType.java
│ ├── LongTypeHandler.java
│ ├── StringTypeHandler.java
│ ├── TypeAliasRegistry.java
│ ├── TypeHandler.java
│ └── TypeHandlerRegistry.java
└── test
├── java
│ └── cn.bugstack.mybatis.test.dao
│ ├── dao
│ │ └── IUserDao.java
│ ├── po
│ │ └── User.java
│ └── ApiTest.java
└── resources
├── mapper
│ └──User_Mapper.xml
└── mybatis-config-datasource.xml

使用策略模式,處理參數(shù)處理器核心類關系,如圖 10-3 所示:

圖 10-3 使用策略模式,處理參數(shù)處理器核心類關系

核心處理主要分為三塊;類型處理、參數(shù)設置、參數(shù)使用;

  • 以定義 TypeHandler 類型處理器策略接口,實現(xiàn)不同的處理策略,包括;Long、String、Integer 等。這里我們先只實現(xiàn)2種類型,讀者在學習過程中,可以按照這個結構來添加其他類型。
  • 類型策略處理器實現(xiàn)完成后,需要注冊到處理器注冊機中,后續(xù)其他模塊參數(shù)的設置還是使用都是從 Configuration 中獲取到 TypeHandlerRegistry 進行使用。
  • 那么有了這樣的策略處理器以后,在進行操作解析 SQL 的時候,就可以按照不同的類型把對應的策略處理器設置到BoundSql#parameterMappings 參數(shù)里,后續(xù)使用也是從這里進行獲取。

2. 入?yún)?shù)校準

這里我們要先解決一個小問題,不知道讀者在我們所實現(xiàn)的源碼中,是否注意到這樣一個參數(shù)的傳遞,如圖 10-4:

圖 10-4 參數(shù)設置時入?yún)@取

  • 這里的參數(shù)傳遞后,需要獲取第0個參數(shù),而且是硬編碼固定的。這是為什么呢?這個第0個參數(shù)是哪來的,我們接口里面調(diào)用的方法,參數(shù)不是一個嗎?就像:User queryUserInfoById(Long id);
  • 其實這個參數(shù)來自于映射器代理類 MapperProxy#invoke 中,因為 invoke 反射調(diào)用的方法,入?yún)⒅惺?Object[] args,所以這個參數(shù)被傳遞到后續(xù)的參數(shù)設置中。而我們的 DAO 測試類是一個已知的固定參數(shù),所以后面硬編碼了獲取了第0個參數(shù)。

JDK 反射調(diào)用方法操作固定方法入?yún)?/p>

  • 那么結合這樣的問題,我們則需要根據(jù)方法的信息,給方法做簽名操作,以便于轉(zhuǎn)換入?yún)⑿畔榉椒ǖ男畔?。比如?shù)組轉(zhuǎn)換為對應的對象。

源碼詳見:cn.bugstack.mybatis.binding.MapperMethod

public class MapperMethod {

public Object execute(SqlSession sqlSession, Object[] args) {
Object result = null;
switch (command.getType()) {
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;
}

/**
* 方法簽名
*/
public static class MethodSignature {

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 {
// 否則,返回一個ParamMap,修改參數(shù)名,參數(shù)名就是其位置
final Map param = new ParamMap();
int i = 0;
for (Map.Entry entry : params.entrySet()) {
// 1.先加一個#{0},#{1},#{2}...參數(shù)
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// ...
}
return param;
}
}

}
}

在映射器方法中 MapperMethod#execute 將原來的直接將參數(shù) args 傳遞給 SqlSession#selectOne 方法,調(diào)整為轉(zhuǎn)換后再傳遞對象。

其實這里的轉(zhuǎn)換操作就是來自于 Method#getParameterTypes 對參數(shù)的獲取和處理,與 args 進行比對。如果是單個參數(shù),則直接返回參數(shù)

Tree 樹結構下的對應節(jié)點值。非單個類型,則需要進行循環(huán)處理,這樣轉(zhuǎn)換后的參數(shù)才能被直接使用。

3. 參數(shù)策略處理器

在 Mybatis 的源碼包中,有一個 type 包,這個包下所提供的就是一套參數(shù)的處理策略集合。它通過定義類型處理器接口、由抽象模板實現(xiàn)并定義標準流程,到提取抽象方法交給子類實現(xiàn),這些子類就是各個類型處理器的具體實現(xiàn)。

3.1 策略接口

源碼詳見:cn.bugstack.mybatis.type.TypeHandler

public interface TypeHandler {

/**
* 設置參數(shù)
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}
  • 首先定義一個類型處理器的接口,這和我們在日常的業(yè)務開發(fā)中是類似的,就像如果是發(fā)貨商品,則定義一個統(tǒng)一標準接口,之后根據(jù)這個接口實現(xiàn)出不同的發(fā)貨策略。
  • 這里設置參數(shù)也是一樣,所有不同類型的參數(shù),都可以被提取出來這些標準的參數(shù)字段和異常,后續(xù)的子類按照這個標準實現(xiàn)即可。Mybatis 源碼中有30+個類型處理。

3.2 模板模式

源碼詳見:cn.bugstack.mybatis.type.BaseTypeHandler

public abstract class BaseTypeHandler implements TypeHandler {

@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 定義抽象方法,由子類實現(xiàn)不同類型的屬性設置
setNonNullParameter(ps, i, parameter, jdbcType);
}

protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}
  • 通過抽象基類的流程模板定義,便于一些參數(shù)的判斷和處理。不過目前我們還不需要那么多的流程校驗,所以這里只是定義和調(diào)用了一個最基本的抽象方法 setNonNullParameter。
  • 不過有一個這樣的結構,可以讓大家更加清楚整個 Mybatis 源碼的框架,便于后續(xù)閱讀或者擴展此部分源碼的時候,有一個框架結構的認知。

3.3 子類實現(xiàn)

源碼詳見:cn.bugstack.mybatis.type.*

/**
* @description Long類型處理器
*/
public class LongTypeHandler extends BaseTypeHandler {

@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}

}

/**
* @description String類型處理器
*/
public class StringTypeHandler extends BaseTypeHandler{

@Override
protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}

}

這里的接口實現(xiàn)舉了個例子,分別是;LongTypeHandler、StringTypeHandler,在 Mybatis 源碼中還有很多其他類型,這里我們暫時不需要實現(xiàn)那么多,只要清楚這個處理過程和編碼方式即可。大家在學習中,可以嘗試再添加幾個其他類型,用于學習驗證。

3.4 類型注冊機

類型處理器注冊機 TypeHandlerRegistry 是我們前面章節(jié)實現(xiàn)的,這里只需要在這個類結構下,注冊新的類型就可以了。

源碼詳見:cn.bugstack.mybatis.type.TypeHandlerRegistry

public final class TypeHandlerRegistry {

private final Map> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
private final Map>> TYPE_HANDLER_MAP = new HashMap<>();
private final Map, TypeHandler> ALL_TYPE_HANDLERS_MAP = new HashMap<>();

public TypeHandlerRegistry() {
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());

register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
}

//...
}
  • 這里在構造函數(shù)中,新增加了 LongTypeHandler、StringTypeHandler 兩種類型的注冊器。
  • 同時可以注意到,無論是對象類型,還是基本類型,都是一個類型處理器。只不過在注冊的時候多注冊了一個。這種操作方式和我們平常的業(yè)務開發(fā)中,也是一樣的。一種是多注冊,另外一種是判斷處理。

4. 參數(shù)構建

相對于前面章節(jié)所完成的內(nèi)容,這個章節(jié)需要對 SqlSourceBuilder 源碼構建器中,創(chuàng)建參數(shù)映射 ParameterMapping 需要添加參數(shù)處理器的內(nèi)容。因為只有這樣才能方便的從參數(shù)映射中獲取到對應類型的處理器進行使用。

那么就需要完善 ParameterMapping 添加 TypeHandler 屬性信息,以及在 ParameterMappingTokenHandler#buildParameterMapping

處理參數(shù)映射時,構建出參數(shù)的映射。這一部分是在上一章節(jié)的實現(xiàn)過程中,細化的完善部分,如圖 10-6:

圖 10-6 構建參數(shù)映射

那么結合上一章節(jié),這里我們開始擴展出類型的設置。同時注意 MetaClass 反射工具類的使用。

源碼詳見:cn.bugstack.mybatis.builder.SqlSourceBuilder

// 構建參數(shù)映射
private ParameterMapping buildParameterMapping(String content) {
// 先解析參數(shù)映射,就是轉(zhuǎn)化成一個 HashMap | #{favouriteSection,jdbcType=VARCHAR}
Map 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("構建參數(shù)映射 property:{} propertyType:{}", property, propertyType);
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
return builder.build();
}
  • 這一部分就是對參數(shù)的細化處理,構建出參數(shù)的映射關系,首先是 if 判斷對應的參數(shù)類型是否在 TypeHandlerRegistry 注冊器中,如果不在則拆解對象,按屬性進行獲取 propertyType 的操作。
  • 這一塊也用到了 MetaClass 反射工具類的使用,它的存在可以讓我們更加方便的處理,否則還需要要再寫反射類進行獲取對象屬性操作。

5. 參數(shù)使用

參數(shù)構建完成后,就可以在 DefaultSqlSession#selectOne 調(diào)用時設置參數(shù)使用了。那么這里的鏈路關系;Executor#query - > SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters 到了 ParameterHandler#setParameters 就可以看到了根據(jù)參數(shù)的不同處理器循環(huán)設置參數(shù)。

源碼詳見:cn.bugstack.mybatis.scripting.defaults.DefaultParameterHandler

public class DefaultParameterHandler implements ParameterHandler {

@Override
public void setParameters(PreparedStatement ps) throws SQLException {
List 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();

// 設置參數(shù)
logger.info("根據(jù)每個ParameterMapping中的TypeHandler設置對應的參數(shù)信息 value:{}", JSON.toJSONString(value));
TypeHandler typeHandler = parameterMapping.getTypeHandler();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}

}
  • 每一個循環(huán)的參數(shù)設置,都是從 BoundSql 中獲取 ParameterMapping 集合進行循環(huán)操作,而這個集合參數(shù)就是我們前面ParameterMappingTokenHandler#buildParameterMapping 構建參數(shù)映射時處理的。
  • 設置參數(shù)時根據(jù)參數(shù)的 parameterObject 入?yún)⒌男畔?,判斷是否基本類型,如果不是則從對象中進行拆解獲取(也就是一個對象A中包括屬性b),處理完成后就可以準確拿到對應的入?yún)⒅盗?。因為在映射器方?MapperMethod 中已經(jīng)處理了一遍方法簽名,所以這里的入?yún)⒕透奖闶褂昧恕?/li>
  • 基本信息獲取完成后,則根據(jù)參數(shù)類型獲取到對應的 TypeHandler 類型處理器,也就是找到 LongTypeHandler、StringTypeHandler 等,確定找到以后,則可以進行對應的參數(shù)設置了 typeHandler.setParameter(ps, i + 1, value, jdbcType) 通過這樣的方式把我們之前硬編碼的操作進行解耦。

五、測試

1. 事先準備

1.1 創(chuàng)建庫表

創(chuàng)建一個數(shù)據(jù)庫名稱為 mybatis 并在庫中創(chuàng)建表 user 以及添加測試數(shù)據(jù),如下:

USER
(
id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
userId VARCHAR(9) COMMENT '用戶ID',
userHead VARCHAR(16) COMMENT '用戶頭像',
createTime TIMESTAMP NULL COMMENT '創(chuàng)建時間',
updateTime TIMESTAMP NULL COMMENT '更新時間',
userName VARCHAR(64),
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '小傅哥');

1.2 配置數(shù)據(jù)源











  • 通過 mybatis-config-datasource.xml 配置數(shù)據(jù)源信息,包括:driver、url、username、password。
  • 在這里 dataSource 可以按需配置成 DRUID、UNPOOLED 和 POOLED 進行測試驗證。

1.3 配置Mapper



  • 這部分暫時不需要調(diào)整,目前還只是一個入?yún)⒌念愋偷膮?shù),后續(xù)我們?nèi)客晟七@部分內(nèi)容以后,則再提供更多的其他參數(shù)進行驗證。

2. 單元測試

源碼詳見:cn.bugstack.mybatis.test.ApiTest

@Before
public void init() throws IOException {
// 1. 從SqlSessionFactory中獲取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
sqlSession = sqlSessionFactory.openSession();
}

因為接下來我們需要驗證兩種不同入?yún)⒌膯卧獪y試,分別來測試基本類型參數(shù)和對象類型參數(shù)。

2.1 基本類型參數(shù)

@Test
public void test_queryUserInfoById() {
// 1. 獲取映射器對象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 測試驗證:基本參數(shù)
User user = userDao.queryUserInfoById(1L);
logger.info("測試結果:{}", JSON.toJSONString(user));
}

07:40:08.531 [main] INFO  c.b.mybatis.builder.SqlSourceBuilder - 構建參數(shù)映射 property:id propertyType:class java.lang.Long
07:40:08.598 [main] INFO c.b.m.s.defaults.DefaultSqlSession - 執(zhí)行查詢 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
07:40:08.875 [main] INFO c.b.m.d.pooled.PooledDataSource - Created connection 183284570.
07:40:08.894 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根據(jù)每個ParameterMapping中的TypeHandler設置對應的參數(shù)信息 value:1
07:40:08.961
文章題目:使用策略模式,調(diào)用參數(shù)處理器
網(wǎng)站鏈接:http://www.dlmjj.cn/article/djcedsg.html