转载自https://blog.csdn.net/u014044812/article/details/78258730
需求
mybatis目前已经内嵌入了springboot中了,这说明其目前在数据访问层的绝对优势。而我们在开发的过程中,往往会在程序中使用枚举(enum) 来表示一些状态或选项,而在数据库中使用数字来存储。这样做的好处是在程序中使用enum更直观的可以知道每个值代表的状态及含义,还可以做国际化的功能。那么这样会带来一个问题那就是:程序中的枚举 与 数据库中的数字 转换问题。
介绍
抱歉,最近因为实在太忙,所以写一半就停了。等有空继续。不将就哈。停了大概一周的时间,在周一的早上继续来完成这篇文章。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
对于enum而言,在mybatis中已经存在了EnumTypeHandler和EnumOrdinalTypeHandler两大处理器,他们都是继承自BaseTypeHandler处理器,那为何不能使用mybatis自定义的enum处理器而是要自己再定义枚举处理器呢?下面为您一步一步剖析。
分析
首先说说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");
}
上面是一个简单的枚举。它包含了7个状态值,当我们没有设置枚举处理器时,mybatis默认使用EnumTypeHandler作为缺省处理器,当需要将枚举存入数据库时它调用的是枚举的name()方法从而获取的是像:disable,enable这样的值存入了数据库,并不是我们想要的(value)value存入数据库,可以看看源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class EnumTypeHandler<E extends Enum> extends BaseTypeHandler {
private Class 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);
}
}
大家可以看到setNonNullParameter方法中ps.setString(i, parameter.name());调用的确实是name()方法。
而在将数据库字段转java Bean字段的时候使用的方法getNullableResult中都是调用Enum.valueOf(this.type, s),而这个方法底层调用的是get(name)方法。
所以EnumTypeHandler对我们来说并不适用,因为它在java Bean转数据库数据时获取的是枚举的name而不是value。
而EnumOrdinalTypeHandler存入数据库的是参数的位置,所以这两个处理器都不是我们想要的。但是我们可以根据EnumTypeHandler的设计思想转变一下,变成符合我们想要的统一处理器。
教程
首先我们定义一个BaseEnumTypeHandler继承BaseTypeHandler,并实现抽象方法。
package com.cmc.base.handler;
import com.cmc.base.enums.EStatus;
import com.cmc.base.utils.EnumUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class BaseEnumTypeHandler<E extends Enum> extends BaseTypeHandler {
private Class<E> type;
public BaseEnumTypeHandler() {}
public BaseEnumTypeHandler(Class<E> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setString(i, parameter.toString());
} else {
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
return get(rs.getString(columnName));
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return get(rs.getString(columnIndex));
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return get(cs.getString(columnIndex));
}
private <E extends Enum<E>> E get(String v) {
if (v == null) return null;
if (StringUtils.isNumeric(v)) {
return get(type, Integer.parseInt(v));
} else {
return Enum.valueOf(type, v);
}
}
private <E extends Enum<E>> E get(Class<E> type, int v) {
Method method = null;
E result = null;
try {
method = type.getMethod("get", int.class);
result = (E)method.invoke(type, v);
} catch (NoSuchMethodException e) {
result = Enum.valueOf(type, String.valueOf(v));
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
}
在上面的方法setNonNullParameter中大家可以看到我使用parameter.toString()替换了原来的parameter.name()方法,其实做到能统一处理枚举的关键就是这里,因为toString方法是Object的方法,所以不管传入的类型是什么都适用。而从数据库中取出的数据转Bean我使用的是和默认的处理器一致的方法get()。
所以我们需要在枚举中定义toString和get方法
package com.cmc.base.enums;
/**
- 状态枚举
- @author chenmc
*/
public enum EStatus {
disable("0"), enable("1"), deleted("2"),
init("10"), start("11"), wait("12"), end("13");
private final String value;
private EStatus(String v) {
this.value = v;
}
public String toString() {
return this.value;
}
public static EStatus get(int v) {
String str = String.valueOf(v);
return get(str);
}
public static EStatus get(String str) {
for (EStatus e : values()) {
if(e.toString().equals(str)) {
return e;
}
}
return null;
}
/*public static String getName(EStatus e, Locale locale) {
return I18N.getEnumName(e, locale);
}*/
}
大家可看到toString方法返回的是当前对象的value值,这样就是我们想要的了。
既然方法都写好了,那下面给大家讲讲如何在springboot中使用。
1.在application.properties或者application.yml中添加
mybatis:
mapperLocations: classpath:mapper/*.xml
typeAliasesPackage: com.cmc.schedule.model.entity
typeHandlersPackage: com.cmc.schedule.model.handler
typeHandlersPackage就是你自定义handler的包名,上面我们定义了一个BaseEnumTypeHandler,往往我们不太会在基类中修改或添加,因为基类可能是多个模块公用的,这时在我们的项目中定义一个EnumTypeHandler去继承我们的BaseEnumTypeHandler。
package com.cmc.schedule.model.handler;
import com.cmc.base.enums.EStatus;
import com.cmc.base.handler.BaseEnumTypeHandler;
import com.cmc.schedule.model.enums.EFeedback;
import org.apache.ibatis.type.MappedTypes;
/**
- @author chenmc
*/