MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler
前言
今天遇上這樣的一個情況,數(shù)據(jù)庫類型與Java對象屬性類型不對應(yīng),這種情況該如何處理
在 MySQL 中,字段的屬性為 BigInt,按道理來說,對應(yīng)Java中的Long類型。
但實(shí)際上項(xiàng)目中與之對應(yīng)的 Java對象中的屬性的類型是 Date類型,直接給我這個廢物當(dāng)頭一棒
而且不是一兩張表,是比較多的表處于Date 和 BigInt混用的情況,
你說要好好用Date就好好用Date,要好好用時間戳就好好用時間戳啊,還混用,類型還不對應(yīng),麻了
(別問這個項(xiàng)目怎么出現(xiàn)這種事情的,就是來了人,又走了人,然后填坑)
保持微笑??(此處口吐芬芳xxxxxx)
一、思考
我想知道出現(xiàn)這種情況,你是如何思考的?
我的思考是,到底是改數(shù)據(jù)庫,還是改程序代碼比較好。
但是無論哪一種我都不敢輕舉妄動,所以我做的第一步是把數(shù)據(jù)庫和代碼備份,確保不會被玩壞。
我也問了同事,他的建議是讓我改程序。
但是怎么說勒,我細(xì)細(xì)比較了改代碼和改程序的麻煩程度,改數(shù)據(jù)表麻煩會少很多,我就在表結(jié)構(gòu)中的Bigint 類型改為 datatime 類型,而且當(dāng)時我的任務(wù),是只局限于一兩張業(yè)務(wù)表,影響范圍不大,引用也不多。
我就興沖沖的把表結(jié)構(gòu)改了,然后把任務(wù)完成了~
等到今天上午,我之前詢問的那個同事也遇到這個問題,他就向上面的經(jīng)理提了一嘴,說時間類型不對,問他標(biāo)準(zhǔn)是哪一種,經(jīng)理說是時間戳,我心里一涼~,麻了,(此處省略一萬句)
聽完,我就苦逼的把表結(jié)構(gòu)改回來了,此時備份就發(fā)生作用了~
還原完數(shù)據(jù)表后,我就打算去改程序代碼了
周一寫 bug,bug 改一周
突然他和我聊到,xxx,你知道MybatisPlus,有什么方法可以做這種轉(zhuǎn)換嗎?
這每一個都要改,太麻煩了,而且業(yè)務(wù)代碼中肯定也用到了,這改起來代價太大了,有沒有注解的方式可以解決轉(zhuǎn)換問題。
很淺顯的思考,但是我能夠感覺到自己的經(jīng)驗(yàn)的不足,對于很多偷懶(思考),我還是差的太遠(yuǎn)了。
二、解決方式
因?yàn)橛玫降?ORM 框架是 MybatisPlus,所以首先找的就是有沒有官方的支持。
繼而就在官網(wǎng)找到一個字段類型處理器,一看才發(fā)現(xiàn),是學(xué)過的東西啊,只怪用的太少,知道的太少啊。

然后根據(jù)這個線索繼續(xù)找,就了解到 MyBatis-Plus 字段類型處理器 TypeHandler
這個 TypeHandler 處于的位置,就是應(yīng)用程序和數(shù)據(jù)庫之間的攔截器,所有的操作,都會走一遍這里。
就翻看源碼,想用一個東西,最快的方式就是看一下源碼的實(shí)現(xiàn)
2.1、TypeHandler源碼
public interface TypeHandler<T> {
/**
* 入庫前的類型轉(zhuǎn)換
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 得到結(jié)果。
* 查詢后的數(shù)據(jù)處理
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
找到接口,看一下源碼中針對已有屬性是如何處理,我們仿寫一份,達(dá)到我們的要求即可啊.

2.2、BaseTypeHandler 源碼
有這么多,我們直接看一下 BaseTypeHandler 是什么樣的處理邏輯,
一方面 base 嗎,基礎(chǔ)嗎,我們就看看基礎(chǔ)是什么樣的處理啦,另外一方面他是抽象類嗎,說明它其他實(shí)現(xiàn)類的基類嗎。
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) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
// 這里就是設(shè)置為 不為 null 時的入庫
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 獲取可為空的結(jié)果。
*/
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;
}
看起來好像很長很多的樣子:當(dāng)我們?nèi)サ裟切┡袛?,精簡一下?/p>
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 {
// 設(shè)置不為null的參數(shù),進(jìn)行入庫 ,此處是抽象類,下層還有實(shí)現(xiàn)類,
// 記住這里,待會帶你看實(shí)現(xiàn)類,你就知道了
setNonNullParameter(ps, i, parameter, jdbcType);
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
// 這里從數(shù)據(jù)庫中獲取到數(shù)據(jù),然后進(jìn)行類型的一個設(shè)置
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);
}
}
2.3、BigIntegerTypeHandler 源碼中的實(shí)現(xiàn)類
public class BigIntegerTypeHandler extends BaseTypeHandler<BigInteger> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, BigInteger parameter, JdbcType jdbcType) throws SQLException {
// 這里是轉(zhuǎn)為 BigDecimal ,所以這里就算 setBigDecimal,
// 那么我們就可以猜測,它還支持其他的方法,Date的話,那就是setDate
ps.setBigDecimal(i, new BigDecimal(parameter));
}
@Override
public BigInteger getNullableResult(ResultSet rs, String columnName) throws SQLException {
BigDecimal bigDecimal = rs.getBigDecimal(columnName);
// 這里是rs.getBigDecimal ,我們待會去試一下能否getDate就可以了
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}
// 這兩個暫時沒有做了解,Debug的時候,斷點(diǎn)沒有執(zhí)行到這,后期再補(bǔ)一塊的知識
// 但是為了以防萬一,我們待會也會照著它的方式將代碼改成這樣
@Override
public BigInteger getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
BigDecimal bigDecimal = rs.getBigDecimal(columnIndex);
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}
@Override
public BigInteger getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
BigDecimal bigDecimal = cs.getBigDecimal(columnIndex);
return bigDecimal == null ? null : bigDecimal.toBigInteger();
}
}
這個實(shí)現(xiàn)類,沒什么代碼,而且就是set、get ,并沒有其他的一些處理邏輯什么的。
那么我們也照這樣的方式實(shí)現(xiàn)一個。
2.4、嘗試
先明確目標(biāo),我們Mysql 中的字段類型 為 BigInt,Java程序中的屬性類型為 Date,
所以我們在入庫的時候就是要將 Date 類型轉(zhuǎn)化為 Long進(jìn)行入庫,
在從數(shù)據(jù)庫中取出來的時候,要從 Long 類型轉(zhuǎn)化為 Date 映射到 JavaBean中
我們直接copy上面的代碼,然后進(jìn)行一些更改
public class MyDateTypeHandler implements TypeHandler<Date>{
/**
* 入庫前的類型轉(zhuǎn)換 即執(zhí)行insert、update方法時會執(zhí)行
*/
@Override
public void setParameter(PreparedStatement ps, int i, Date parameter,
JdbcType jdbcType) throws SQLException {
log.info("setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType)....");
log.info("[{}],[{}]",parameter,jdbcType);
ps.setLong(i, parameter.getTime());
}
/**
* 查詢后的數(shù)據(jù)處理
* 這就是查詢出來,進(jìn)行映射的時候,會執(zhí)行這段代碼
*/
@Override
public Date getResult(ResultSet rs, String columnName) throws SQLException {
log.info("getResult(ResultSet rs, String columnName)....",columnName);
return new Date(rs.getLong(columnName));
}
@Override
public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
log.info("getResult(ResultSet rs, int columnIndex)....");
return new Date(rs.getLong(columnIndex));
}
@Override
public Date getResult(CallableStatement cs, int columnIndex)
throws SQLException {
log.info("getResult(CallableStatement cs, int columnIndex)....");
return cs.getDate(columnIndex);
}
}
咋一眼好像成功啦,但是我們忽略了一個問題,就是MybatisPlus怎么知道它的存在?
那些默認(rèn)允許進(jìn)行相互進(jìn)行類型轉(zhuǎn)換的Handler,它在程序啟動的時候,就已經(jīng)被注冊了。
但是我們寫了這個類,一方面沒有被MybatisPlus知曉,另一方面還沒有指明給誰使用,我們又該怎么使用?
基于此,我寫了一個小Demo,希望大家能夠弄明白,以后遇上也能夠解決一些問題
三、實(shí)踐案例
實(shí)現(xiàn)目標(biāo):
Mysql 中的表的字段為Bigint,Java程序中為 Date 類型,我們希望還是可以一如既往的使用
MybatisPlus的方法,實(shí)現(xiàn)save、list類似這種方法的正常調(diào)用,而無需我在保存的時候,將前端傳過來的數(shù)據(jù)手動轉(zhuǎn)換為時間戳,再存放至數(shù)據(jù)庫。查詢時亦是如此
3.1、數(shù)據(jù)庫

數(shù)據(jù)庫
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for handler_test -- ---------------------------- DROP TABLE IF EXISTS `handler_test`; CREATE TABLE `handler_test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `date` bigint(50) NOT NULL COMMENT '存時間戳', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of handler_test -- ---------------------------- INSERT INTO `handler_test` VALUES (1, '測試數(shù)據(jù)1', 1659967236); INSERT INTO `handler_test` VALUES (2, '測試數(shù)據(jù)2', 1659967236); INSERT INTO `handler_test` VALUES (3, '測試插入數(shù)據(jù)', 1659968162926); INSERT INTO `handler_test` VALUES (4, '測試插入數(shù)據(jù)', 1659972053771); INSERT INTO `handler_test` VALUES (5, '測試插入數(shù)據(jù)', 1659972815670); SET FOREIGN_KEY_CHECKS = 1;
3.2、相關(guān)代碼
我只貼出了相關(guān)的代碼,其余代碼在源碼倉庫中有,別慌,家人們
service
public interface IHandlerTestService extends IService<HandlerTest> {
}
TypeHandler 實(shí)現(xiàn)類
/**
* @author Ning zaichun
*/
@Slf4j
@MappedJdbcTypes({JdbcType.BIGINT}) //對應(yīng)數(shù)據(jù)庫類型
@MappedTypes({Date.class}) //java數(shù)據(jù)類型
public class MyDateTypeHandler implements TypeHandler<Date>{
/**
* 入庫前的類型轉(zhuǎn)換
*/
@Override
public void setParameter(PreparedStatement ps, int i, Date parameter,
JdbcType jdbcType) throws SQLException {
log.info("setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType)....");
log.info("[{}],[{}]",parameter,jdbcType);
ps.setLong(i, parameter.getTime());
}
/**
* 查詢后的數(shù)據(jù)處理
*/
@Override
public Date getResult(ResultSet rs, String columnName) throws SQLException {
log.info("getResult(ResultSet rs, String columnName)....");
log.info("[{}]",columnName);
return new Date(rs.getLong(columnName));
}
@Override
public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
log.info("getResult(ResultSet rs, int columnIndex)....");
return new Date(rs.getLong(columnIndex));
}
@Override
public Date getResult(CallableStatement cs, int columnIndex)
throws SQLException {
log.info("getResult(CallableStatement cs, int columnIndex)....");
return cs.getDate(columnIndex);
}
}
實(shí)體類的修改,有兩點(diǎn),
第一點(diǎn),需要在實(shí)體類上加上
@TableName(value = "handler_test",autoResultMap = true)
value 是對應(yīng)表名,autoResultMap 說的
是否自動構(gòu)建 resultMap 并使用,
只生效與 mp 自動注入的 method,
如果設(shè)置 resultMap 則不會進(jìn)行 resultMap 的自動構(gòu)建并注入,
只適合個別字段 設(shè)置了 typeHandler 或 jdbcType 的情況
第二點(diǎn)就是要在需要處理的字段上加上
@TableField(typeHandler = MyDateTypeHandler.class)
注解,class就寫我們自己編寫 Handler.class即可
@Data
@TableName(value = "handler_test",autoResultMap = true)
@EqualsAndHashCode(callSuper = false)
public class HandlerTest implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
/**
* 存時間戳
*/
@TableField(typeHandler = MyDateTypeHandler.class)
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date date;
}
弄完上述這兩點(diǎn),我們還有一個問題,我之前提到一個注冊,雖然我們指定了,也寫好了,但實(shí)際上,還并沒有注冊到一個存儲 TypeHandler 一個 Map 集合中去的,也就是說Mybatis 在遇到的時候,其實(shí)還是不知道它的存在的~。
但其實(shí)只需要在配置文件中加一行即可,原諒我這么繞圈子,只是希望說明白這是一步步得來的

type-handlers-package 后面填寫的是我們Handler 存放的包路徑。
有這一步即可。
3.3、測試
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = HandlerApplication.class)
public class HandlerServiceTest {
@Autowired
IHandlerTestService handlerTestService;
@Test
public void test1(){
List<HandlerTest> list = handlerTestService.list();
list.forEach(System.out::println);
}
@Test
public void test2(){
HandlerTest handlerTest = new HandlerTest();
handlerTest.setDate(new Date());
handlerTest.setName("測試插入數(shù)據(jù)");
handlerTestService.save(handlerTest);
}
}

測試插入
==> Preparing: SELECT name,date FROM handler_test
==> Parameters:
<== Columns: name, date
<== Row: 測試數(shù)據(jù)1, 1659967236
2022-08-08 23:55:25.854 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659967236
<== Row: 測試數(shù)據(jù)2, 1659967236
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659967236
<== Row: 測試插入數(shù)據(jù), 1659968162926
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659968162926
<== Row: 測試插入數(shù)據(jù), 1659972053771
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659972053771
<== Row: 測試插入數(shù)據(jù), 1659972815670
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659972815670
<== Row: 測試插入數(shù)據(jù), 1659974106847
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659974106847
<== Row: 測試插入數(shù)據(jù), 1659974125542
2022-08-08 23:55:25.855 INFO 7368 --- [ main] com.nzc.demo.handler.MyDateTypeHandler : getResult(ResultSet rs, String columnName)....
1659974125542
<== Total: 7
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@145113f]
HandlerTest(name=測試數(shù)據(jù)1, date=Tue Jan 20 13:06:07 CST 1970)
HandlerTest(name=測試數(shù)據(jù)2, date=Tue Jan 20 13:06:07 CST 1970)
HandlerTest(name=測試插入數(shù)據(jù), date=Mon Aug 08 22:16:02 CST 2022)
HandlerTest(name=測試插入數(shù)據(jù), date=Mon Aug 08 23:20:53 CST 2022)
HandlerTest(name=測試插入數(shù)據(jù), date=Mon Aug 08 23:33:35 CST 2022)
HandlerTest(name=測試插入數(shù)據(jù), date=Mon Aug 08 23:55:06 CST 2022)
HandlerTest(name=測試插入數(shù)據(jù), date=Mon Aug 08 23:55:25 CST 2022)
2022-08-08 23:55:25.863 INFO 7368 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closing ...
2022-08-08 23:55:25.869 INFO 7368 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closed
以上就是MyBatisPlus TypeHandler自定義字段類型轉(zhuǎn)換Handler的詳細(xì)內(nèi)容,更多關(guān)于MyBatisPlus字段類型轉(zhuǎn)換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決IDEA上循環(huán)依賴報錯問題Error:java: Annotation processing&n
這篇文章主要介紹了解決IDEA上循環(huán)依賴報錯問題Error:java: Annotation processing is not supported for module cycles,具有很好的參考價值,希望對大家有所幫助2023-10-10
Mybatis-Plus可能導(dǎo)致死鎖的問題分析及解決辦法
這篇文章給大家主要介紹了Mybatis-Plus可能導(dǎo)致死鎖的問題分析及解決辦法,文中通過代碼示例給大家介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2023-12-12
使用Java進(jìn)行Json數(shù)據(jù)的解析(對象數(shù)組的相互嵌套)
下面小編就為大家?guī)硪黄褂肑ava進(jìn)行Json數(shù)據(jù)的解析(對象數(shù)組的相互嵌套)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
SpringBoot Jpa企業(yè)開發(fā)示例詳細(xì)講解
這篇文章主要介紹了SpringBoot Jpa企業(yè)開發(fā)示例,Jpa可以通過實(shí)體類生成數(shù)據(jù)庫的表,同時自帶很多增刪改查方法,大部分sql語句不需要我們自己寫,配置完成后直接調(diào)用方法即可,很方便2022-11-11
詳解Java程序并發(fā)的Wait-Notify機(jī)制
這篇文章主要介紹了詳解Java程序并發(fā)的Wait-Notify機(jī)制,多線程并發(fā)是Java編程中的重要部分,需要的朋友可以參考下2015-07-07
SpringBoot關(guān)閉druid的頁面和添加密碼驗(yàn)證方式
這篇文章主要介紹了SpringBoot關(guān)閉druid的頁面和添加密碼驗(yàn)證方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

