類型處理器(TypeHandler)
無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
通過類型處理器(TypeHandler),可以實現javaBean以某種方式存入數據庫中,抑或是從數據庫取出的數據如何映射為javaBean。
通過繼承BaseTypeHandler類,我們可以定制這個類型處理器,已實現枚舉類或是一個javaBean的存取和寫入。
內置的枚舉處理器
mybatis內置了兩個枚舉類型處理器,EnumTypeHandler和EnumOrdinalTypeHandler,這兩個類型都不好用,一般也是我們自己實現枚舉的類型處理器。
EnumTypeHandler存入數據庫的是枚舉的name,EnumOrdinalTypeHandler存入數據庫的是枚舉的位置。例如下方的枚舉,當我們有一個枚舉值是EStatus.init時,這時我們使用mybatis的EnumTypeHandler存入數據庫的是"init"字符串;而EnumOrdinalTypeHandler存入的是3,因為init是第四個值,第一個值disable的index是0。
public enum EStatus {
disable("0"), enable("1"), deleted("2"),
init("10"), start("11"), wait("12"), end("13");
}
EnumTypeHandler源碼
public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private final Class<E> type;
//采用父類的構造方法,會獲取到注解MappedTypes中的value,作為type
public EnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
} else {
this.type = type;
}
}
//寫入數據庫時調用
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setString(i, parameter.name());
} else {
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
}
}
//以下三個方法是在存入時候調用,一個是根據列名獲取值,一個是根據列索引位置獲取值,最后一個是存儲過程。
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String s = rs.getString(columnName);
return s == null ? null : Enum.valueOf(this.type, s);
}
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
return s == null ? null : Enum.valueOf(this.type, s);
}
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
return s == null ? null : Enum.valueOf(this.type, s);
}
}
自定義枚舉類處理
1、實現BaseTypeHandler接口,重寫方法
public class GenderHandler extends BaseTypeHandler<Gender> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Gender gender, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, gender.getCode());
}
@Override
public Gender getNullableResult(ResultSet resultSet, String s) throws SQLException {
return Gender.getGender(resultSet.getString(s));
}
@Override
public Gender getNullableResult(ResultSet resultSet, int i) throws SQLException {
return Gender.getGender(resultSet.getString(i));
}
@Override
public Gender getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return Gender.getGender(callableStatement.getString(i));
}
}
2、在application.yml中加入配置,掃描handler組件
mybatis:
# 配置mybatis的resultType別名,默認是別名為小寫
type-aliases-package: com.lexiaoyao.mybatisdemo.domain
# 配置掃描的xml文件位置
mapper-locations: classpath:mybatis/mapper/*.xml
# mybatis詳細配置文件
config-location: classpath:mybatis/mybatis-config.xml
# 掃描handler組件
type-handlers-package: com.lexiaoyao.mybatisdemo.handler
通用枚舉處理器
note 這里將mysql數據庫里字段定義為char(1)
1、定義枚舉類的接口,統一格式
public interface BaseEnum<E extends Enum<?>, T> {
T getCode();//code為存入數據庫的值
String getInfo();//info為實際意義
}
2、定義枚舉類
public enum Gender implements BaseEnum<Gender, String> {
Male("1", "男"),
Female("0", "女"),
Unknown("3", "保密");
private String code;
private String info;
Gender(String code, String info) {
this.code = code;
this.info = info;
}
public static Gender getGender(String code) {
return Arrays.stream(Gender.values()).filter(i -> i.getCode().equals(code)).findAny().orElse(null);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
3、編寫統一的枚舉映射handler
public class NormalEnumHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
private Class<E> enumType;
private E[] enums;
public NormalEnumHandler(Class<E> type) {
if (type == null)
throw new IllegalArgumentException("Type argument cannot be null");
this.enumType = type;
this.enums = type.getEnumConstants();//獲取所有枚舉數組
if (this.enums == null)
throw new IllegalArgumentException(type.getSimpleName()
+ " does not represent an enum type.");
}
public NormalEnumHandler() {
}
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
preparedStatement.setString(i, (String) e.getCode());
} else {
preparedStatement.setObject(i, e.getCode(), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet resultSet, String s) throws SQLException {
return resultSet.wasNull() ? null : locateEnumStatus(resultSet.getString(s));
}
@Override
public E getNullableResult(ResultSet resultSet, int i) throws SQLException {
return resultSet.wasNull() ? null : locateEnumStatus(resultSet.getString(i));
}
@Override
public E getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.wasNull() ? null : locateEnumStatus(callableStatement.getString(i));
}
private E locateEnumStatus(String value) {
return Arrays.stream(enums)
.filter(i -> i.getCode().equals(value))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("未知的枚舉類型:" + value + ",請核對" + enumType.getSimpleName()));
}
}
4、相應的掃描包下只需寫一個Handler,並繼承NormalEnumHandler
@MappedTypes(value = {Gender.class})
public class NormalHandler<E extends BaseEnum> extends NormalEnumHandler<E> {
public NormalHandler(Class<E> type) {
super(type);
}
}
要新加入枚舉時候,只需要在MappedTypes的數據里添加一個即可。
5、配置掃描包
mybatis:
# 配置mybatis的resultType別名,默認是別名為小寫
type-aliases-package: com.lexiaoyao.mybatisdemo.domain
# 配置掃描的xml文件位置
mapper-locations: classpath:mybatis/mapper/*.xml
# mybatis詳細配置文件
config-location: classpath:mybatis/mybatis-config.xml
# 掃描handler組件
type-handlers-package: com.lexiaoyao.mybatisdemo.handler