MyBatis自定義TypeHandler如何解決字段映射問題
MyBatis自定義TypeHandler字段映射
小林子:串哥
串一串:干哈啊,又來
小林子:如果MySQL一張表中一個字段存儲的數(shù)據(jù)格式是"1,2,3,4,5",也就是逗號分隔的,我如何能讓別的使用者在無感知的情況下,只用List<Integer>來傳輸和接收?持久層用的MyBatis。你滴明白我的意思嗎?
串一串:不明白
小林子:…
串一串:你知道MyBatis中有一個類叫BaseTypeHandler嗎?這個類可以滿足你的需求。
小林子:具體要怎么做?我有點懵,沒接觸過這個類,它是干嘛的?
串一串:我們來看個例子
創(chuàng)建一張表待用
create table qfant_message.demo ( id int auto_increment primary key, name varchar(10) null, hobbies varchar(100) null );
然后新建一個SpringBoot工程,在工程中引入mybatis-generator,我們使用它來生成Mapper文件,如果不會的話,自行谷歌,這里不做詳細講解,下一篇再說。
在生成Mapper文件之前,我們先定義一個處理字段hobbies的TypeHandler,命名為ListTypeHandler,這里問個問題:為什么不叫HobbiesTypeHandler呢?這樣應(yīng)該和字段更加貼合啊。
原因是這個Handler不僅僅是能處理hobbies,它可以處理所有相同情況的任何表的任何字段。
這個類繼承自org.apache.ibatis.type.BaseTypeHandler
看下簡化后的內(nèi)容:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setNull(i, jdbcType.TYPE_CODE);
} else {
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
return getNullableResult(rs, columnName);
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
return getNullableResult(rs, columnIndex);
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
return getNullableResult(cs, columnIndex);
}
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
我們可以看到,不論是通過哪一個getResult方法獲取數(shù)據(jù),都是去調(diào)用下面的幾個抽象方法,MyBatis幫我們實現(xiàn)了很多常用的類型的Handler,都在org.apache.ibatis.type包里面,截圖看下吧,免得以為在忽悠你

小林子:那這里面有沒有能滿足我這個需求的Handler?如果有的話我就直接用了
串一串:你去看看,這里我說一下怎么重復造輪子
根據(jù)上述內(nèi)容,我們就可以來寫ListTypeHandler了,在寫之前先整理一下思路:
因為我們實體類中hobbies屬性是java.util.List類型的,而數(shù)據(jù)庫表中hobbies字段是varchar類型的,所以我們需要在更新(插入)之前和查詢之后對數(shù)據(jù)進行一次轉(zhuǎn)換
- 插入之前:將List中的數(shù)據(jù)轉(zhuǎn)換為以逗號分隔的字符串
- 查詢之后:將逗號分隔的字符串轉(zhuǎn)換為List結(jié)構(gòu)
思路理順了
我們來看看具體的代碼
package cc.kevinlu.handler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import com.qfant.sms.data.model.DemoDO;
@MappedJdbcTypes(value = { JdbcType.VARCHAR })
//① @MappedTypes(value = DemoDO.class)
public class ListTypeHandler extends BaseTypeHandler<List<Integer>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<Integer> parameter, JdbcType jdbcType)
throws SQLException {
String d = parameter.stream().map(v -> String.valueOf(v)).collect(Collectors.joining(","));
ps.setString(i, d);
}
@Override
public List<Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String values = rs.getString(columnName);
return getResults(values);
}
@Override
public List<Integer> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String values = rs.getString(columnIndex);
return getResults(values);
}
@Override
public List<Integer> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String values = cs.getString(columnIndex);
return getResults(values);
}
private List<Integer> getResults(String values) {
if (StringUtils.isNotBlank(values)) {
String[] data = values.split(",");
return Arrays.stream(data).mapToInt(v -> Integer.parseInt(v)).boxed().collect(Collectors.toList());
}
return new ArrayList<>();
}
}
然后生成對應(yīng)的Mapper和DO實體類,剛才說了我們使用的是mybatis-generator,這里直接貼上<table>的相關(guān)配置
<table tableName="demo" domainObjectName="DemoDO" mapperName="DemoMapper"
enableCountByExample="true"
enableDeleteByExample="true" enableInsert="true" enableSelectByExample="true"
enableUpdateByExample="true"
selectByExampleQueryId="true" enableSelectByPrimaryKey="true">
<generatedKey column="id" sqlStatement="MySql" identity="true"/>
<columnOverride column="hobbies" property="hobbies" jdbcType="VARCHAR" javaType="java.util.List"
typeHandler="cc.kevinlu.handler.ListTypeHandler"/>
</table>
注意這里我們使用標簽<columnOverride>重寫了column的定義,這里一定要指明javaType和typeHandler,javaType的目的是讓生成的DemoDO的屬性hobbies聲明為java.util.List,如果不加該字段的話,默認會根據(jù)jdbcType="VARCHAR"生成java.lang.String類型,然后typeHandler指向我們剛創(chuàng)建的ListTypeHandler,這樣在生成DemoMapper.xml的時候,會在對應(yīng)的字段上加上typeHandler,否則需要我們挨個兒位置的去修改,xml中的內(nèi)容如下:
<insert id="insert" parameterType="cc.kevinlu.data.model.DemoDO">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into demo (name, hobbies)
values (
#{name,jdbcType=VARCHAR},
#{hobbies,jdbcType=VARCHAR,typeHandler=com.qfant.sms.handler.ListTypeHandler}
)
</insert>
小林子:是不是這樣就可以直接使用了?
串一串:你有沒有注意到ListTypeHandler上有一個被注釋掉的注解,把那個注釋打開,然后value指向DO實體類即可,這個注釋的意思是指定該Handler映射的java類,value是一個數(shù)組,可以指定一組映射類,當然也可以不指定。即使指定了,也可以用于其他類型,然后@MappedJdbcTypes映射的是jdbc的類型
小林子:那現(xiàn)在是不是可以測試啦?走一波~
@Resource
private DemoMapper demoMapper;
@Test
public void index() {
List<DemoDO> data = demoMapper.selectByExample(new DemoDOExample());
data.forEach(System.out::println);
}
輸出:
Demo1DO [Hash = 3112387, id=1, name=123, hobbies=[1, 2, 3], serialVersionUID=1]
Demo1DO [Hash = 3294608, id=2, name=456, hobbies=[4, 5, 6], serialVersionUID=1]
總結(jié)一下
1.MyBatis之所以能解決MySQL字段和Java屬性之間的匹配,全都依賴于org.apache.ibatis.type.BaseTypeHandler<T>抽象類,在該類中定義了3個獲取結(jié)果的方法、1個更新的方法和4個抽象方法,我們可以自定義該抽象類來實現(xiàn)這個4個抽象方法進行Java類的屬性和表字段的映射,可以做一些相關(guān)的處理。
2.MyBatis在org.apache.ibatis.type包中定義了常用的字段映射Handler,并且在服務(wù)啟動的時候會在TypeHandlerRegistry構(gòu)造方法中將其注冊到一個Map中,而TypeHandlerRegistry是在MyBatis的核心類Configuration中進行的實例化
3.自定義的Handler可以全局通用,不受限于某一個字段或某一個Java類
4.在生成Mapper時使用<columnOverride>重寫column聲明,然后需要指定jdbcType和typeHandler
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- MyBatis自定義TypeHandler實現(xiàn)字段加密解密
- MyBatis類型處理器TypeHandler的作用及說明
- MyBatis中TypeHandler的使用教程詳解
- MyBatis-Plus?中?typeHandler?的使用實例詳解
- SpringBoot中MyBatis使用自定義TypeHandler的實現(xiàn)
- 解決Mybatis-plus自定義TypeHandler查詢映射結(jié)果一直為null問題
- Mybatis中TypeHandler使用小結(jié)
- Mybatis的TypeHandler實現(xiàn)數(shù)據(jù)加解密詳解
- Mybatis中自定義TypeHandler處理枚舉的示例代碼
- MyBatisPlus自定義JsonTypeHandler實現(xiàn)自動轉(zhuǎn)化JSON問題
- MyBatis中TypeHandler基本用法與示例
相關(guān)文章
Spring?Boot?Nacos?實現(xiàn)不停服發(fā)布過程詳解
這篇文章主要為大家介紹了Spring?Boot?Nacos實現(xiàn)不停服發(fā)布過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
springboot?整合?clickhouse的實現(xiàn)示例
本文主要介紹了springboot?整合?clickhouse的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
SpringBoot快速實現(xiàn)接口消息加密的過程詳解
在項目中,為了保證數(shù)據(jù)的安全,我們常常會對傳遞的數(shù)據(jù)進行加密,常用的加密算法包括對稱加密(AES)和非對稱加密(RSA),博主選取碼云上最簡單的API加密項目進行下面的講解,需要的朋友可以參考下2023-11-11
SSH結(jié)合jquery實現(xiàn)三級聯(lián)動效果
這篇文章主要為大家詳細介紹了SSH結(jié)合jquery實現(xiàn)三級聯(lián)動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
淺談java中replace()和replaceAll()的區(qū)別
這篇文章主要介紹了java中replace()和replaceAll()的區(qū)別,兩者都是常用的替換字符的方法,感興趣的小伙伴們可以參考一下2015-11-11
關(guān)于SpringBoot在有Ajax時候不跳轉(zhuǎn)的問題解決
最近在使用Ajax來發(fā)送一些數(shù)據(jù)給后臺一個Controller,但是遇到些問題,所以下面這篇文章主要給大家介紹了關(guān)于SpringBoot在有Ajax時候不跳轉(zhuǎn)問題的解決辦法,需要的朋友可以參考下2022-05-05

