一、介紹
mybatis我們都用過,但很多都只是侷限於利用xml來配置statement來進行後端CRUD,對於XML的配置和優化其實可以更深一層地挖掘。(團隊開發中,需要搭建項目的微服務權限架構,爲了後端用戶和角色權限的更好維護,使用到了轉化器)
預備知識:
-----瞭解mybatis是通過掃描Mapper接口文件和對應的XML資源,在查詢時候以mapper接口文件中的方法名,來匹配xml中的statement對象,進行查詢。
-----瞭解mybais的CRUD在通過configuration加載配置,啓動sqlSessionFacory()後,通過這個工廠來創建sqlSession會話進行溝通
-----瞭解mybais的預加載是在創建sqlSession後通過獲取mapper的方式定位到xml中對應的方法。
-----瞭解到mybatis在進行定位後開始通過BaseExcetor與cacheExcetor執行(動態SQL解析和查詢結果的緩存維護)。
-----瞭解到mybatis的動態解析的需要經過[參數映射,SQL解析,SQL執行,結果映射]
(圖來自博友)
說明:對於一些特殊參數處理,我們可以在參數映射階段對特殊的參數的隱射方式進行優化和改造。mybatis中提供瞭如下的一個對象,我們可以通過繼承並重寫入參和出參的處理方式,從而實現特殊字段的參數隱射和結果映射。
BaseTypeHandler<T>
二、轉化器解析
概述:這一部分主要是講解BaseTypeHandler<T>怎麼用以及理解!
說明:類型轉化主要是用於當你的邏輯層的數據類型與數據庫表中對應的字段的數據類型不一致的場景。比如常見的性別類型,在數據庫爲數字參數,查詢到邏輯層時自動通過字段轉化器匹配轉化爲中文【結果映射】,中文名稱在存入數據庫時自動通過轉化器將中文轉化爲數字代碼【參數映射】。比如數據類型不一致的轉化,集合處理等。我們知道某些存儲的數據類型類似[java.util.Set]在數據庫是沒有對應的數據類型,雖然一般情況下我們不會做這種操作,但如果必須要我們通常是選擇存儲爲[Vachar]類型。這個時候也可以通過轉化器實現通用的轉化管理。處理思路依舊是【參數映射】,【結果映射】分別做出入管理。
頂層接口:
public interface TypeHandler<T> {
void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
T getResult(ResultSet var1, String var2) throws SQLException;
T getResult(ResultSet var1, int var2) throws SQLException;
T getResult(CallableStatement var1, int var2) throws SQLException;
}
抽象接口:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
protected Configuration configuration;
public BaseTypeHandler() {
}
}
說明:以上爲類型轉化器的頂層接口與抽象接口,我們要繼承的便是BaseTypeHandler<T>。其中<T>類型傳入的是我們邏輯層的數據類型。在實現繼承後,我們將重寫如下的四個方法。
(因爲電腦截圖範圍限制,文章爲了描述,註釋與備註並沒有做統一規範。)
說明1:在重寫完這個方法後,如果xml的statement中存在配置,那麼在進行【參數映射】的時候,【預處理權柄】將會自動走入此類。邏輯層調用所指定的參數將會通過如下方法進行處理,處理的結果作爲最終數據庫的入參,交給數據庫執行:
/**
* CYQ:插入時候將數據轉化爲字符串 [xxx,xxxx,xxxxx] 存入數據庫
* @param preparedStatement
* @param i
* @param integers
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Set<Integer> integers, JdbcType jdbcType) throws SQLException {
StringBuffer result = new StringBuffer();
result.append("[");
if (!CollectionUtils.isEmpty(integers)){
for (Integer integerParam : integers){
result.append(integerParam).append(",");
}
result.deleteCharAt(result.length()-1);
}
result.append("]");
preparedStatement.setString(i,result.toString());
}
說明2:當我們數據庫存入數據後,查詢出來的數據便是我們數據庫的數據格式,獲取之後轉化爲我們需要的參數【結果映射】,通常我們獲取數據的方式,有通過數據庫字段名去匹配獲取,通過索引字段去獲取,通過存儲過程去獲取。因此我們需要重寫獲取的這三種類型的映射方法。
/**
* CYQ:通過字段名查詢後轉化
* @param resultSet
* @param s
* @return
* @throws SQLException
*/
@Override public HashSet<Integer> getNullableResult(ResultSet resultSet, String s) throws SQLException {
return getStringToSet(resultSet.getString(s));
}
/**
* CYQ:通過字段索引查詢後轉化
* @param resultSet
* @param i
* @return
* @throws SQLException
*/
@Override public Set<Integer> getNullableResult(ResultSet resultSet, int i) throws SQLException {
return getStringToSet(resultSet.getString(i));
}
/**
* CYQ:通過存儲過程查詢後轉化
* @param callableStatement
* @param i
* @return
* @throws SQLException
*/
@Override public Set<Integer> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return getStringToSet(callableStatement.getString(i));
}
/**
* CYQ:將數據庫中字符串類型轉化爲Set<Integer>集合(兼具備去重的責任)
* @param columnValue
* @return
*/
public HashSet<Integer> getStringToSet(String columnValue) {
if (StringUtils.isBlank(columnValue)){
return null;
}
HashSet<Integer> integerHashSet = new HashSet<Integer>();
Integer[] integers = toObject(columnValue);
if (integers.length == 0){
return null;
}
for (Integer s: integers){
integerHashSet.add(s);
}
return integerHashSet;
}
//將數據庫字段轉化爲數組
public Integer[] toObject(String content) {
if (content != null && !content.isEmpty()) {
try {
return (Integer[]) mapper.readValue(content, Integer[].class);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
到此處我們的轉化器便處理完成了,那麼應該如何使用!
三、配置和使用
說明: XML文件的配置因爲Mybaits的用法很多,本次只說明基本的配置方式,如圖:(免註冊)
方式一:<resultMap></resultMap>標籤
<resultMap id="BaseResultMap" type="com.xxxxx.xxxx.comom.vo.SysAdminInfo">
<id column="y_pk_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
<result column="user_name" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="pass_word" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="last_login_ip" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="last_login_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="head_picture" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="role_ids" jdbcType="VARCHAR" property="roleIds" javaType="java.util.Set" typeHandler="com.xxxx.xxxx.framework.common.mybatis.StringSetTypeHandler"/>
<result column="create_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="create_by" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="update_by" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="del_flag" jdbcType="BIT" javaType="java.lang.Boolean" />
</resultMap>
說明:此處的配置我們以role_ids字段,role_ids本身在實體類中爲Set類型,因爲數據庫限制,我們只能需要以字符串的形式維護進去。因此,使用了 【StringSetTypeHandler】做了轉化。配置如下:
<result column="role_ids" jdbcType="VARCHAR" property="roleIds" javaType="java.util.Set" typeHandler="com.xxxx.xxxx.framework.common.mybatis.StringSetTypeHandler"/>
注意:mybatis不會通過數據庫信息去窺探數據類型和邏輯層實體類的類型是否一致,只會在對應不上的時候報異常。因此我們需要明確告知其 對應的實體類類型【javaType】與數據庫表數據類型【jdbcType】;此處使用property是因爲數據庫字段與表字段不一致,我們需要進行協調。(使用property必須使用resultMap標籤中的result參數來作爲標記)
<if test="roleIds != null" >
#{roleIds,jdbcType=VARCHAR,javaType=java.util.Set,typeHandler=com.xxxx.xxx.framework.common.mybatis.StringSetTypeHandler},
</if>
說明:在XML中實行了轉化器的插入或者查詢引用方式如上。
補充說明:如果需要使用property則不能使用xml裏面的構造器。