最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收穫,於是將自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!
本篇部落格主要講解在MyBatis中如何使用型別處理器。
1. 明確需求
在設計之初,sys_role表的enabled欄位有2個可選值,其中0 代表禁用,1代表啟用,而且實體類中我們使用的是Interger型別:
/**
* 有效標誌
*/
private Integer enabled;
public Integer getEnabled() {
return enabled;
}
public void setEnabled(Integer enabled) {
this.enabled = enabled;
}
如果要新增或者更新角色資訊,我們肯定要校驗enabled欄位的值必須是0或者1,所以最初的部分程式碼可能是這樣的:
if (sysRole.getEnabled() == 0 || sysRole.getEnabled() == 1) {
sysRoleMapper.updateById(sysRole);
sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(0, sysRole.getEnabled());
} else {
throw new Exception("無效的enabled值");
}
這種硬編碼的方式不僅看起來不友好,而且不利於後期維護,如果維護的程式設計師脾氣不好,還會罵你,哈哈。
所以我們的需求就是,拒絕硬編碼,使用友好的編碼方式來校驗enabled欄位的值是否有效。
2. 使用MyBatis提供的列舉型別處理器
我們通常會使用列舉來解決這種場景。
首先新建com.zwwhnly.mybatisaction.type包,然後在該包下新建列舉Enabled:
package com.zwwhnly.mybatisaction.type;
public enum Enabled {
/**
* 禁用
*/
disabled,
/**
* 啟用
*/
enabled;
}
其中,disabled對應的索引為0,enabled對應的索引為1。
然後將SysRole類中原來為Integer型別的enabled欄位修改為:
/**
* 有效標誌
*/
private Enabled enabled;
public Enabled getEnabled() {
return enabled;
}
public void setEnabled(Enabled enabled) {
this.enabled = enabled;
}
此時原本硬編碼的程式碼就可以修改為:
if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
sysRoleMapper.updateById(sysRole);
sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
} else {
throw new Exception("無效的enabled值");
}
雖然上面的程式碼很完美的解決了硬編碼的問題,但此時又引出一個新的問題:
資料庫並不能識別Enabled列舉型別,在新增,更新或者作為查詢條件時,需要將列舉值轉換為資料庫中的int型別,在查詢資料時,需要將資料庫的int型別的值轉換為Enabled列舉型別。
帶著這個問題,我們在SysRoleMapperTest測試類中新增如下測試方法:
@Test
public void testUpdateById() {
SqlSession sqlSession = getSqlSession();
try {
SysRoleMapper sysRoleMapper = sqlSession.getMapper(SysRoleMapper.class);
// 先查詢出id=2的角色,然後修改角色的enabled值為disabled
SysRole sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.enabled, sysRole.getEnabled());
// 修改角色的enabled為disabled
sysRole.setEnabled(Enabled.disabled);
if (sysRole.getEnabled() == Enabled.disabled || sysRole.getEnabled() == Enabled.enabled) {
sysRoleMapper.updateById(sysRole);
sysRole = sysRoleMapper.selectById(2L);
Assert.assertEquals(Enabled.disabled, sysRole.getEnabled());
} else {
throw new Exception("無效的enabled值");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
執行測試程式碼,發現丟擲如下異常:
Error querying database. Cause: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'enabled' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.zwwhnly.mybatisaction.type.Enabled.1
這是因為MyBatis在處理Java型別和資料庫型別時,使用TypeHandler(型別處理器)對這兩者進行轉換。
MyBatis為Java型別和資料庫JDBC中的常用型別型別提供了TypeHandler介面的實現。
MyBatis在啟動時會載入所有的JDBC對應的型別處理器,在處理列舉型別時預設使用org.apache.ibatis.type.EnumTypeHandler處理器,這個處理器會將列舉型別轉換為字串型別的字面值使用,對於Enabled列舉來說,就是“disabled"和”enabled"字串。
而資料庫中enabled欄位的型別是int,所以在查詢到角色資訊將int型別的值1轉換為Enabled型別報錯。
那麼如何解決這個問題呢?
MyBatis還提供了另一個列舉處理器:org.apache.ibatis.type.EnumOrdinalTypeHandler,這個處理器使用列舉的索引進行處理,可以解決此處轉換報錯的問題。
使用這個處理器,需要在之前的resources/mybatis-config.xml中新增如下配置:
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>
再次執行測試程式碼,測試通過,輸出日誌如下:
DEBUG [main] - ==> Preparing: SELECT id,role_name,enabled,create_by,create_time FROM sys_role WHERE id = ?
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time
TRACE [main] - <== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <== Total: 1
DEBUG [main] - ==> Preparing: UPDATE sys_role SET role_name = ?,enabled = ?,create_by=?, create_time=? WHERE id=?
DEBUG [main] - ==> Parameters: 普通使用者(String), 0(Integer), 1(Long), 2019-06-27 18:21:12.0(Timestamp), 2(Long)
DEBUG [main] - <== Updates: 1
從日誌中可以看出,在查詢角色資訊時,MyBatis將1轉換為了Enabled.enabled,在更新角色資訊時,MyBatis將Enabled.disabled轉換為了0。
3. 使用自定義的型別處理器
假設enabled欄位的值既不是列舉的字面值,也不是列舉的索引值,此時org.apache.ibatis.type.EnumTypeHandler
和org.apache.ibatis.type.EnumOrdinalTypeHandler
都不能滿足我們的需求,這種情況下我們就需要自己來實現型別處理器了。
首先修改下列舉類Enabled程式碼:
package com.zwwhnly.mybatisaction.type;
public enum Enabled {
/**
* 啟用
*/
enabled(1),
/**
* 禁用
*/
disabled(0);
private final int value;
private Enabled(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
然後在com.zwwhnly.mybatisaction.type包下新建型別處理器EnabledTypeHandler:
package com.zwwhnly.mybatisaction.type;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* Enabled型別處理器
*/
public class EnabledTypeHandler implements TypeHandler<Enabled> {
private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>();
public EnabledTypeHandler() {
for (Enabled enabled : Enabled.values()) {
enabledMap.put(enabled.getValue(), enabled);
}
}
@Override
public void setParameter(PreparedStatement preparedStatement, int i, Enabled enabled, JdbcType jdbcType) throws SQLException {
preparedStatement.setInt(i, enabled.getValue());
}
@Override
public Enabled getResult(ResultSet resultSet, String s) throws SQLException {
Integer value = resultSet.getInt(s);
return enabledMap.get(value);
}
@Override
public Enabled getResult(ResultSet resultSet, int i) throws SQLException {
Integer value = resultSet.getInt(i);
return enabledMap.get(value);
}
@Override
public Enabled getResult(CallableStatement callableStatement, int i) throws SQLException {
Integer value = callableStatement.getInt(i);
return enabledMap.get(value);
}
}
自定義型別處理器實現了TypeHandler介面,重寫了介面中的4個方法,並且在無參建構函式中遍歷了列舉型別Enabled並對欄位enabledMap進行了賦值。
想要使用自定義的型別處理器,也需要在resources/mybatis-config.xml中新增如下配置:
<typeHandlers>
<!--其他配置-->
<typeHandler handler="com.zwwhnly.mybatisaction.type.EnabledTypeHandler"
javaType="com.zwwhnly.mybatisaction.type.Enabled"/>
</typeHandlers>
執行測試程式碼,輸出日誌和上面的輸出日誌一樣,這裡不再重複貼出。
4. 原始碼及參考
原始碼地址:https://github.com/zwwhnly/mybatis-action.git,歡迎下載。
劉增輝《MyBatis從入門到精通》
5. 最後
打個小廣告,歡迎掃碼關注微信公眾號:「申城異鄉人」,定期分享Java技術乾貨,讓我們一起進步。