MyBatis之TypeHandler解析
MyBatis作為一個ORM框架,在實現物件到關係資料庫對映的過程中,一個無法避免的問題就是Java型別和JDBC型別之間的相互轉換,而TypeHandler的作用就在於此,其作用是實現Java型別向JDBC型別之間的轉換。
從上面的描述上來看,TypeHandler的作用物件是一個物件屬性,從Java型別向JDBC型別之間的轉換這樣的說法或許過於抽象,舉個例子來說,比如我們有個物件的屬性是String陣列,我想在插入資料庫的時候將這個屬性在插入資料庫的時候是以一個varchar的屬性記錄,陣列中每個元素以逗號相隔,這是從Java型別轉換到JDBC;而從資料庫獲取這個物件的屬性的時候又能夠分割開逗號返回物件String陣列,這是從JDBC轉換到Java型別。
從實現層次上來說TypeHandler的功能,就是在我們使用MyBatis插入一個物件資料的時候,物件的每條屬性是在經過如何處理後放入JDBC的SQL語句中,在取出一條資料庫記錄的時候,每一列的資料又是經過如何的處理放入物件的屬性中的,這兩個方面我們完全可以從MyBatis提供TypeHandler
org.apache.ibatis.type.TypeHandler
是所有TypeHandler的基類,裡面有四個方法
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;
其中setParameter是將一個物件的屬性經過轉換後插入到PreparedStatement,後面三個方法是將從ResultSet中取出的值處理後返回給物件的屬性(分別是通過列名,列索引來獲取值,最後一個適用於儲存過程)。而org.apache.ibatis.type.BaseTypeHandler
則實現了TypeHandler,做了一些null值上的處理,一般情況下我們自定義的型別處理器直接繼承自BaseTypeHandler,並覆蓋一下四個方法
public abstract void setNonNullParameter (PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;
public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;
public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
對於一下簡單的TypeHandler MyBatis已經預設提供了,對於一些比較難處理的,比如陣列,容器,自定義簡單物件需要我們自己自定義TypeHandler進行型別處理。
自定義StringArrayTypeHandler
上圖顯示了在構建MyBatis程式的一般過程,其中橙色的線是我們在使用TypeHandler的過程中的一些重要步驟,我在圖中做了數字標註。
編寫StringArrayTypeHandler
首先第一步是編寫屬性的型別轉換器,我們這裡用編寫的String陣列的型別轉換器作為例子。假設JavaBean是Student有如下屬性
public class Student
{
private int id;
private String name;
private int age;
private SexEnum sex;
private String[] interests;
//getter and setter
}
其中interests屬性是一個數組,我們第一步編寫StringArrayTypeHandler,它繼承自BaseTypeHandler
@Override
/**
* 將java型別轉換為JDBC型別
*/
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String[] strings, JdbcType jdbcType) throws SQLException {
StringBuilder builder=new StringBuilder();
for(String item: strings){
builder.append(item+",");
}
builder.deleteCharAt(builder.length()-1);
System.out.println(builder.toString());
preparedStatement.setString(i,builder.toString());
}
@Override
/**
*通過列名獲取Java型別的值
*/
public String[] getNullableResult(ResultSet resultSet, String s) throws SQLException {
String result=resultSet.getString(s);
if(result!=null){
return result.split(",");
}else{
return null;
}
}
編寫Mapper介面
第二步是先寫業務需求的介面,我們這裡定義了插入和查詢兩個介面
public interface StudentMapper {
int insertStudent(Student student);
Student findStudentById(int id);
}
配置Mapper檔案
定義完業務介面就可以配置Mapper檔案,這個是我們使用TypeHandler的地方,有兩處使用到了我們定義的StringArrayTypeHandler,第一處是在我們插入值的引數處,使用了typeHandler引數。
<insert id="insertStudent" parameterType="TypeHandler.Student" useGeneratedKeys="true" keyProperty="id">
insert into student(stu_name,stu_age,stu_interests,stu_sex) values(#{name},#{age},
#{interests,typeHandler=TypeHandler.StringArrayTypeHandler},
#{sex,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler})
</insert>
另一處是在select的resultMap中
<resultMap id="stuResult" type="TypeHandler.Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="stu_age" property="age"/>
<result column="stu_sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
<result column="stu_interests" property="interests" typeHandler="TypeHandler.StringArrayTypeHandler"/>
</resultMap>
這兩個方法Mapper方法的呼叫會引起我們自定義的StringArrayTypeHandler的setNonNullParameter和getNullableResult方法的呼叫。
Mapper配置檔案和Mapper介面直接的管理是Mapper配置檔案來維護的,Mapper配置檔案namespace指定了Mapper介面的全稱限名,並且對應的select,insert子標籤的id對應於Mapper介面的方法名。
基礎配置檔案載入Mapper檔案
第四步就是在MyBatis的基礎配置檔案中載入Mapper配置檔案
<mapper resource="TypeHandler/StudentMapper.xml"/>
如果是非顯示使用TypeHandler還要在基礎配置檔案中註冊TypeHandler,顯示使用則不用。顯示使用是指直接指定TypeHandler的全稱限名,非顯示使用只指定type和jdbcType,讓框架去尋找合適的TypeHandler
使用Mapper介面進行測試
這一階段需要經過載入基礎配置檔案,獲取SqlSessionFactory,開啟SqlSession,getMapper執行sql語句的過程,至此整個過程結束,程式順利執行
遇到的問題
- 沒有宣告別名的類一定要寫成全稱限名
- 主鍵回填不僅要設定useGeneratedKeys=true,還要設定keyProperty
EnumOrdinalTypeHandler和EnumTypeHandler
這兩個都是MyBatis內建的處理Enum型別屬性的型別轉換器,其區別在於在存入資料庫的時候EnumOrdinalTypeHandler是呼叫ordinal()方法獲取索引儲存,而EnumTypeHandler是呼叫name()方法獲取名稱進行儲存,取回的時候也是同樣策略的逆過程,比如我們上面使用了EnumOrdinalTypeHandler,其對應的列舉型別如下,而資料庫儲存的stu_sex為對應的索引0或1
public enum SexEnum {
FEMALE("女"),MALE("男");
private String sexName;
SexEnum(String sexName){
this.sexName=sexName;
}
public String getSexName() {
return sexName;
}
public void setSexName(String sexName) {
this.sexName = sexName;
}
}