MyBatis自定義TypeHandler如何解決字段映射問題
MyBatis自定義TypeHandler字段映射
小林子:串哥
串一串:干哈啊,又來
小林子:如果MySQL一張表中一個(gè)字段存儲(chǔ)的數(shù)據(jù)格式是"1,2,3,4,5",也就是逗號(hào)分隔的,我如何能讓別的使用者在無感知的情況下,只用List<Integer>來傳輸和接收?持久層用的MyBatis。你滴明白我的意思嗎?
串一串:不明白
小林子:…
串一串:你知道MyBatis中有一個(gè)類叫BaseTypeHandler嗎?這個(gè)類可以滿足你的需求。
小林子:具體要怎么做?我有點(diǎn)懵,沒接觸過這個(gè)類,它是干嘛的?
串一串:我們來看個(gè)例子
創(chuàng)建一張表待用
create table qfant_message.demo ( id int auto_increment primary key, name varchar(10) null, hobbies varchar(100) null );
然后新建一個(gè)SpringBoot工程,在工程中引入mybatis-generator
,我們使用它來生成Mapper文件,如果不會(huì)的話,自行谷歌,這里不做詳細(xì)講解,下一篇再說。
在生成Mapper文件之前,我們先定義一個(gè)處理字段hobbies
的TypeHandler
,命名為ListTypeHandler
,這里問個(gè)問題:為什么不叫HobbiesTypeHandler
呢?這樣應(yīng)該和字段更加貼合啊。
原因是這個(gè)Handler不僅僅是能處理hobbies
,它可以處理所有相同情況的任何表的任何字段。
這個(gè)類繼承自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; }
我們可以看到,不論是通過哪一個(gè)getResult
方法獲取數(shù)據(jù),都是去調(diào)用下面的幾個(gè)抽象方法,MyBatis幫我們實(shí)現(xiàn)了很多常用的類型的Handler,都在org.apache.ibatis.type
包里面,截圖看下吧,免得以為在忽悠你
小林子:那這里面有沒有能滿足我這個(gè)需求的Handler?如果有的話我就直接用了
串一串:你去看看,這里我說一下怎么重復(fù)造輪子
根據(jù)上述內(nèi)容,我們就可以來寫ListTypeHandler
了,在寫之前先整理一下思路:
因?yàn)槲覀儗?shí)體類中hobbies
屬性是java.util.List
類型的,而數(shù)據(jù)庫表中hobbies
字段是varchar
類型的,所以我們需要在更新(插入)之前和查詢之后對(duì)數(shù)據(jù)進(jìn)行一次轉(zhuǎn)換
- 插入之前:將List中的數(shù)據(jù)轉(zhuǎn)換為以逗號(hào)分隔的字符串
- 查詢之后:將逗號(hào)分隔的字符串轉(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<>(); } }
然后生成對(duì)應(yīng)的Mapper和DO實(shí)體類,剛才說了我們使用的是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>
注意這里我們使用標(biāo)簽<columnOverride>
重寫了column的定義,這里一定要指明javaType
和typeHandler
,javaType
的目的是讓生成的DemoDO
的屬性hobbies
聲明為java.util.List
,如果不加該字段的話,默認(rèn)會(huì)根據(jù)jdbcType="VARCHAR"
生成java.lang.String
類型,然后typeHandler
指向我們剛創(chuàng)建的ListTypeHandler
,這樣在生成DemoMapper.xml的時(shí)候,會(huì)在對(duì)應(yīng)的字段上加上typeHandler
,否則需要我們挨個(gè)兒位置的去修改,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上有一個(gè)被注釋掉的注解,把那個(gè)注釋打開,然后value指向DO實(shí)體類即可,這個(gè)注釋的意思是指定該Handler映射的java類,value是一個(gè)數(shù)組,可以指定一組映射類,當(dāng)然也可以不指定。即使指定了,也可以用于其他類型,然后@MappedJdbcTypes映射的是jdbc的類型
小林子:那現(xiàn)在是不是可以測試?yá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個(gè)獲取結(jié)果的方法、1個(gè)更新的方法和4個(gè)抽象方法,我們可以自定義該抽象類來實(shí)現(xiàn)這個(gè)4個(gè)抽象方法進(jìn)行Java類的屬性和表字段的映射,可以做一些相關(guān)的處理。
2.MyBatis在org.apache.ibatis.type
包中定義了常用的字段映射Handler
,并且在服務(wù)啟動(dòng)的時(shí)候會(huì)在TypeHandlerRegistry
構(gòu)造方法中將其注冊(cè)到一個(gè)Map中,而TypeHandlerRegistry
是在MyBatis的核心類Configuration
中進(jìn)行的實(shí)例化
3.自定義的Handler
可以全局通用,不受限于某一個(gè)字段或某一個(gè)Java類
4.在生成Mapper時(shí)使用<columnOverride>
重寫column
聲明,然后需要指定jdbcType
和typeHandler
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot開發(fā)存儲(chǔ)服務(wù)器實(shí)現(xiàn)過程詳解
這篇文章主要為大家介紹了SpringBoot開發(fā)存儲(chǔ)服務(wù)器實(shí)現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java 實(shí)戰(zhàn)項(xiàng)目錘煉之醫(yī)院門診收費(fèi)管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+html+jdbc+mysql實(shí)現(xiàn)一個(gè)醫(yī)院門診收費(fèi)管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11java實(shí)現(xiàn)文本文件刪除空行的示例分享
這篇文章主要介紹了java實(shí)現(xiàn)文本文件刪除空行的示例,需要的朋友可以參考下2014-04-04關(guān)于Java的ArrayList數(shù)組自動(dòng)擴(kuò)容機(jī)制
這篇文章主要介紹了關(guān)于Java的ArrayList數(shù)組自動(dòng)擴(kuò)容機(jī)制,ArrayList底層是基于數(shù)組實(shí)現(xiàn)的,是一個(gè)動(dòng)態(tài)數(shù)組,自動(dòng)擴(kuò)容,不是線程安全的,只能用在單線程環(huán)境下,需要的朋友可以參考下2023-05-05詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理
這篇文章主要介紹了詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11ActiveMQ結(jié)合Spring收發(fā)消息的示例代碼
這篇文章主要介紹了ActiveMQ結(jié)合Spring收發(fā)消息的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10SpringBoot文件上傳功能的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot文件上傳功能的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08request如何獲取body的json數(shù)據(jù)
這篇文章主要介紹了request如何獲取body的json數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06