實(shí)體類JSON字段的終極轉(zhuǎn)換思路
哈嘍,各位代碼戰(zhàn)士們,我是Jensen,一個(gè)夢(mèng)想著和大家一起在代碼的海洋里遨游,順便撿起那些散落的知識(shí)點(diǎn)的程序員小伙伴。
聽說大家都不愛當(dāng)“接碼俠”,筆者在接盤別人代碼之時(shí)也常意難平,我的心情大部分是這樣的:
今天看看數(shù)據(jù)庫(kù)JSON字段是怎么映射到代碼上來的。
本文涉及技術(shù)棧:類型處理器、D3Boot。
一、都有些什么寫法
1.用String映射
這種是最簡(jiǎn)單最普遍的寫法,JSON不就是字符串嘛,能存能取就行了,存數(shù)據(jù)庫(kù)跟存銀行沒什么區(qū)別,數(shù)據(jù)庫(kù)存一條,銀行存一“張不夠花”。
前后端要用?對(duì)不起,自己解析去吧,用Json工具類,轉(zhuǎn)完七七四十九遍以后,別把這個(gè)字符串玩壞就行。
完了以后,隔壁組的數(shù)據(jù)分析獅張開了她的獠牙看向你……
2.用JSONObject映射
那我用世界500強(qiáng)公司的com.alibaba.fastjson.JSONObject總可以了吧。
可以是可以,但麻煩是真麻煩,取元素出來還得轉(zhuǎn)換成具體的類型,只能通過字符串Key取值,還得引入個(gè)fastjson包,對(duì)于沒什么阿里信仰的同學(xué)可能不是那么的友好。
Hutool包也有個(gè)同類名的cn.hutool.json.JSONObject,一不小心把兩個(gè)給搞混就GG了。
要是再遇到你的同類把一個(gè)實(shí)體從頭傳到尾,再作為DTO提供Jar包出去就……
3.自定義類型處理器
其實(shí),在Mybatis這個(gè)主流ORM框架中,有這么一個(gè)抽象類:
org.apache.ibatis.type.BaseTypeHandler<T>
它是 MyBatis 框架中一個(gè)非常重要的類,它提供了一個(gè)通用的基類,用于自定義類型處理器(TypeHandler)。
在 MyBatis 中,類型處理器負(fù)責(zé)在 Java 類型和 JDBC 類型之間的轉(zhuǎn)換。當(dāng)你想要對(duì)某種類型進(jìn)行特殊的處理,或者 MyBatis 默認(rèn)的類型處理器不能滿足你的需求時(shí),你可以自定義一個(gè)類型處理器。
以下是 BaseTypeHandler的一些關(guān)鍵點(diǎn):
- 泛型:BaseTypeHandler<T> 使用泛型 T 來指定它所處理的 Java 類型。
- 繼承:自定義類型處理器通常繼承自 BaseTypeHandler<T>,并且重寫幾個(gè)核心方法。
- 核心方法——void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):此方法用于將 Java 類型設(shè)置到 SQL 語句的參數(shù)中。
- 核心方法——T getResult(ResultGetter getter, Class<T> clazz):此方法用于從 SQL 查詢的結(jié)果集中獲取 Java 類型。
- 類型轉(zhuǎn)換:BaseTypeHandler 的子類可以實(shí)現(xiàn)具體的類型轉(zhuǎn)換邏輯,包括日期時(shí)間格式的轉(zhuǎn)換、枚舉類型的轉(zhuǎn)換、復(fù)雜對(duì)象的序列化和反序列化等。
- 配置:自定義的類型處理器可以在 MyBatis 的映射文件中配置,與特定的字段或結(jié)果集映射相關(guān)聯(lián)。
- 重用:由于 BaseTypeHandler 是泛型的,因此可以為不同的類型創(chuàng)建不同的處理器,同時(shí)重用相同的處理邏輯。
通過繼承 BaseTypeHandler<T> 并實(shí)現(xiàn)必要的方法,你可以完全控制數(shù)據(jù)在 Java 對(duì)象和數(shù)據(jù)庫(kù)之間的轉(zhuǎn)換過程,這為處理復(fù)雜的業(yè)務(wù)邏輯提供了靈活性。
通過類型處理器,我們可以映射成任意類型,存取數(shù)據(jù)其實(shí)沒有那么的麻煩。
不錯(cuò)不錯(cuò),就它了。
二、還是先造個(gè)輪子吧
public abstract class BaseTypeHandler<T> extends org.apache.ibatis.type.BaseTypeHandler<T> {
/**
* 獲取實(shí)際的類型,用于后續(xù)的類型注冊(cè)與類型判斷
*/
public Class<T> type() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), 0);
}
/**
* 把指定類型轉(zhuǎn)換為字符串類型,對(duì)應(yīng)寫庫(kù)
*/
protected abstract String convert(T obj);
/**
* 把字符串類型解析成指定類型,對(duì)應(yīng)讀庫(kù)
*/
protected abstract T parse(String result);
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) return;
ps.setString(i, this.convert(parameter));
}
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String str = rs.getString(columnName);
return str == null || str.isEmpty() ? null : this.parse(str);
}
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String str = rs.getString(columnIndex);
return str == null || str.isEmpty() ? null : this.parse(str);
}
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String str = cs.getString(columnIndex);
return str == null || str.isEmpty() ? null : this.parse(str);
}
}
接著,繼承這個(gè)抽象類再寫一個(gè)子抽象類:
/**
* 自定義POJO類型轉(zhuǎn)換器父類:json/varchar <-> T
* 繼承該類并加@Component注解,放在entity下的typehandlers目錄,PO類無需在@TableField注解上加類型處理器,能自動(dòng)轉(zhuǎn)換
*
* @param <T> 自定義POJO,一般以VO命名,與Model同目錄,注意T不能是List集合或Map類型,但可以是數(shù)組類型(以實(shí)現(xiàn)對(duì)對(duì)象數(shù)組的互轉(zhuǎn))
* @author Jensen
* @公眾號(hào) 架構(gòu)師修行錄
*/
@Slf4j(topic = "### BASE-DATA : TypeHandlers ###")
public abstract class JsonStringTypeHandler<T> extends BaseTypeHandler<T> {
private Class<?> componentType;
private Object[] componentArray;
public JsonStringTypeHandler() {
Class<T> tClass = type();
// 判斷具體的類型是否為數(shù)組
if (tClass.isArray()) {
Class<Object[]> arrayClass = (Class<Object[]>) tClass;
this.componentType = arrayClass.getComponentType();
this.componentArray = (Object[]) Array.newInstance(componentType, 0);
}
log.info("Loading {}, type: {}", this.getClass().getSimpleName(), type().getSimpleName());
}
@Override
protected String convert(T obj) {
// 轉(zhuǎn)換為Json字符串
return JsonKit.toJson(obj);
}
@Override
protected T parse(String json) {
if (this.componentType != null) {
// Json解析為對(duì)象數(shù)組
List<?> list = JsonKit.toList(json, this.componentType);
if (list == null) return null;
return (T) list.toArray(this.componentArray);
}
// Json解析為對(duì)象
return JsonKit.toObject(json, type());
}
}
這個(gè)JsonString類型解析器,既可以轉(zhuǎn)換對(duì)象,也可以轉(zhuǎn)換對(duì)象數(shù)組(注意是Array不是List),不需要再分開記了。
不要心急,還沒完成,我們還需要把所有實(shí)現(xiàn)它的類型處理器注冊(cè)到類型處理器注冊(cè)器內(nèi):
/**
* Mybatis 配置
*
* @author Jensen
* @公眾號(hào) 架構(gòu)師修行錄
**/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class)
@AllArgsConstructor
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisConfiguration.class})
public class MybatisPlusConfig implements InitializingBean {
final MybatisPlusProperties mybatisPlusProperties;
// 把所有實(shí)現(xiàn)了BaseTypeHandler的子類都注入進(jìn)來
final List<BaseTypeHandler> jsonStringTypeHandlers;
@Override
public void afterPropertiesSet() {
MybatisConfiguration configuration = mybatisPlusProperties.getConfiguration();
if (configuration == null) {
configuration = new MybatisConfiguration();
mybatisPlusProperties.setConfiguration(configuration);
}
// 核心的注冊(cè)邏輯
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// JavaObject、JavaArray
if (jsonStringTypeHandlers != null && !jsonStringTypeHandlers.isEmpty()) {
for (BaseTypeHandler baseTypeHandler : jsonStringTypeHandlers) {
typeHandlerRegistry.register(baseTypeHandler.type(), baseTypeHandler);
}
}
}
}
這里用到了Spring的依賴注入,一次注入多個(gè)Bean(記筆記,又是一個(gè)解耦神器),后續(xù)實(shí)現(xiàn)的自定義類型處理器只需要受Spring管理就行了。
至此,大功告成!
三、看看怎么用吧
@Data
@TableName("order_info")
public class OrderInfo {
// 用戶快照
private UserInfoVO userInfo;
// 商品列表快照
private GoodsSpuVO[] goodsSpus;
}
沒錯(cuò),就是這么定義,實(shí)體類啥都不用加了。
但我們還是要另外針對(duì)性加兩個(gè)類型處理器,只是這個(gè)類型處理器是不需要被實(shí)體類依賴的,隨便你放哪兒都行,我把它們安放在entity.typehandlers目錄下:
@Component
public class UserInfoTypeHandler extends JsonStringTypeHandler<UserInfoVO> {
// 類名隨便起,啥都不用寫,加個(gè)@Component受Spring管理就行了
}
@Component
public class GoodsSpusTypeHandler extends JsonStringTypeHandler<GoodsSpuVO[]> {
// 類名隨便起,啥都不用寫,加個(gè)@Component受Spring管理就行了
}
好了,咱就是說,以后再也不需要煩怎么存取的問題了,有道是,有頭發(fā)誰要吃咖喱?
四、OneMoreThing
Gitee源碼地址:https://gitee.com/jensvn/d3boot。
今天又是干貨滿滿的一天,果然在程序員的眼里,對(duì)象是最好處理的。