问题

在编码过程中,经常会遇到用某个数值来表示某种状态、类型或者阶段的情况,比如有这样一个枚举:

 
public enum ComputerState {
OPEN(10), //开启
CLOSE(11), //关闭
OFF_LINE(12), //离线
FAULT(200), //故障
UNKNOWN(255); //未知 private int code;
ComputerState(int code) { this.code = code; }
}

通常我们希望将表示状态的数值存入数据库,即ComputerState.OPEN存入数据库取值为10

探索

首先,我们先看看MyBatis是否能够满足我们的需求。 MyBatis内置了两个枚举转换器分别是:org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler

EnumTypeHandler

这是默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串,即将ComputerState.OPEN转换OPEN

EnumOrdinalTypeHandler

顾名思义这个转换器将枚举实例的ordinal属性作为取值,即ComputerState.OPEN转换为0,ComputerState.CLOSE转换为1。 使用它的方式是在MyBatis配置文件中定义:

 
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.example.entity.enums.ComputerState"/>
</typeHandlers>

以上的两种转换器都不能满足我们的需求,所以看起来要自己编写一个转换器了。

方案

MyBatis提供了org.apache.ibatis.type.BaseTypeHandler类用于我们自己扩展类型转换器,上面的EnumTypeHandlerEnumOrdinalTypeHandler也都实现了这个接口。

1. 定义接口

我们需要一个接口来确定某部分枚举类的行为。如下:

 
public interface BaseCodeEnum {
int getCode();
}

该接口只有一个返回编码的方法,返回值将被存入数据库。

2. 改造枚举

就拿上面的ComputerState来实现BaseCodeEnum接口:

 
public enum ComputerState implements BaseCodeEnum{
OPEN(10), //开启
CLOSE(11), //关闭
OFF_LINE(12), //离线
FAULT(200), //故障
UNKNOWN(255); //未知 private int code;
ComputerState(int code) { this.code = code; } @Override
public int getCode() { return this.code; }
}

3. 编写一个转换工具类

现在我们能顺利的将枚举转换为某个数值了,还需要一个工具将数值转换为枚举实例。

 
public class CodeEnumUtil {

    public static <E extends Enum<?> & BaseCodeEnum> E codeOf(Class<E> enumClass, int code) {
E[] enumConstants = enumClass.getEnumConstants();
for (E e : enumConstants) {
if (e.getCode() == code)
return e;
}
return null;
}
}

4. 自定义类型转换器

准备工作做的差不多了,是时候开始编写转换器了。 BaseTypeHandler<T> 一共需要实现4个方法:

  1. void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
  2. T getNullableResult(ResultSet rs, String columnName) 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
  3. T getNullableResult(ResultSet rs, int columnIndex) 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
  4. T getNullableResult(CallableStatement cs, int columnIndex) 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型

我是这样实现的:

 
public class CodeEnumTypeHandler<E extends Enum<?> & BaseCodeEnum> extends BaseTypeHandler<BaseCodeEnum> {

    private Class<E> type;

    public CodeEnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
} @Override
public void setNonNullParameter(PreparedStatement ps, int i, BaseCodeEnum parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter.getCode());
} @Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
} else {
try {
return CodeEnumUtil.codeOf(type, i);
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.",
ex);
}
}
} @Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int i = rs.getInt(columnIndex);
if (rs.wasNull()) {
return null;
} else {
try {
return CodeEnumUtil.codeOf(type, i);
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.",
ex);
}
}
} @Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int i = cs.getInt(columnIndex);
if (cs.wasNull()) {
return null;
} else {
try {
return CodeEnumUtil.codeOf(type, i);
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.",
ex);
}
}
}
}

5. 使用

接下来需要指定哪个类使用我们自己编写转换器进行转换,在MyBatis配置文件中配置如下:

 
<typeHandlers>
<typeHandler handler="com.example.typeHandler.CodeEnumTypeHandler" javaType="com.example.entity.enums.ComputerState"/>
</typeHandlers>

搞定! 经测试ComputerState.OPEN被转换为10,ComputerState.UNKNOWN被转换为255,达到了预期的效果。

6. 优化

在第5步时,我们在MyBatis中添加typeHandler用于指定哪些类使用我们自定义的转换器,一旦系统中的枚举类多了起来,MyBatis的配置文件维护起来会变得非常麻烦,也容易出错。如何解决呢? 在Spring中我们可以使用JavaConfig方式来干预SqlSessionFactory的创建过程,来完成动态的转换器指定。 思路

  1. 通过sqlSessionFactory.getConfiguration().getTypeHandlerRegistry()取得类型转换器注册器
  2. 扫描所有实体类,找到实现了BaseCodeEnum接口的枚举类
  3. 将实现了BaseCodeEnum的类注册使用CodeEnumTypeHandler进行转换。

实现如下:

MyBatisConfig.java

 
@Configuration
@ConfigurationProperties(prefix = "mybatis")
public class MyBatisConfig { private String configLocation; private String mapperLocations; @Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ResourcesUtil resourcesUtil) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource); // 设置配置文件地址
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factory.setConfigLocation(resolver.getResource(configLocation));
factory.setMapperLocations(resolver.getResources(mapperLocations)); SqlSessionFactory sqlSessionFactory = factory.getObject(); // ----------- 动态加载实现BaseCodeEnum接口的枚举,使用CodeEnumTypeHandler转换器 // 取得类型转换注册器
TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry(); // 扫描所有实体类
List<String> classNames = resourcesUtil.list("com/example", "/**/entity"); for (String className : classNames) {
// 处理路径成为类名
className = className.replace('/', '.').replaceAll("\\.class", "");
// 取得Class
Class<?> aClass = Class.forName(className, false, getClass().getClassLoader()); // 判断是否实现了BaseCodeEnum接口
if (aClass.isEnum() && BaseCodeEnum.class.isAssignableFrom(aClass)) {
// 注册
typeHandlerRegistry.register(className, "com.example.typeHandler.CodeEnumTypeHandler");
}
} // --------------- end return sqlSessionFactory;
} public String getConfigLocation() {
return configLocation;
} public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
} public String getMapperLocations() {
return mapperLocations;
} public void setMapperLocations(String mapperLocations) {
this.mapperLocations = mapperLocations;
}
}

ResourcesUtil.java

 

@Component
public class ResourcesUtil { private final ResourcePatternResolver resourceResolver; public ResourcesUtil() {
this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
} /**
* 返回路径下所有class
*
* @param rootPath 根路径
* @param locationPattern 位置表达式
* @return
* @throws IOException
*/
public List<String> list(String rootPath, String locationPattern) throws IOException {
Resource[] resources = resourceResolver.getResources("classpath*:" + rootPath + locationPattern + "/**/*.class");
List<String> resourcePaths = new ArrayList<>();
for (Resource resource : resources) {
resourcePaths.add(preserveSubpackageName(resource.getURI(), rootPath));
}
return resourcePaths;
}
}

结束了

以上就是我对如何在MyBatis中优雅的使用枚举的探索。如果你还有更优的解决方案,请一定在评论中告知,万分感激。

转自:https://segmentfault.com/a/1190000010755321

如何在MyBatis中优雅的使用枚举的更多相关文章

  1. 如何在mybatis 中使用In操作

    如何在mybatis 中使用In操作 假如我们想使用这样一个sql 语句,但是这样的sql语句有IN这样的操作.在我们的mybatis中有相对应的操作 SELECT * FROM product_db ...

  2. 如何在 Swoole 中优雅的实现 MySQL 连接池

    如何在 Swoole 中优雅的实现 MySQL 连接池 一.为什么需要连接池 ? 数据库连接池指的是程序和数据库之间保持一定数量的连接不断开, 并且各个请求的连接可以相互复用, 减少重复连接数据库带来 ...

  3. 如何在Vue中优雅的使用防抖节流

    1. 什么是防抖节流 防抖:防止重复点击触发事件 首先啥是抖? 抖就是一哆嗦!原本点一下,现在点了3下!不知道老铁脑子是不是很有画面感!哈哈哈哈哈哈 典型应用就是防止用户多次重复点击请求数据. 代码实 ...

  4. 如何在 Swift 中优雅地处理 JSON

    阅读目录 在Swift中使用JSON的问题 开始 基础用法 枚举(Enumeration) 下标(Subscripts) 打印 调试与错误处理 后记   因为Swift对于类型有非常严格的控制,它在处 ...

  5. 如何在K8S中优雅的使用私有镜像库 (Docker版)

    前言 在企业落地 K8S 的过程中,私有镜像库 (专用镜像库) 必不可少,特别是在 Docker Hub 开始对免费用户限流之后, 越发的体现了搭建私有镜像库的重要性. 私有镜像库不但可以加速镜像的拉 ...

  6. 如何在mybatis中引用java中的常量和方法

    转自:http://www.68idc.cn/help/jiabenmake/qita/20140821125261.html 在mybatis的映射xml文件调用java类的方法: 1. SELEC ...

  7. 如何在php中优雅的地调用python程序

    1.准备工作   安装有python和php环境的电脑一台. 2.书写程序. php程序如下 我们也可以将exec('python test.py') 换成 system('python test.p ...

  8. Spring Security:如何在Postman中优雅地测试后端API(前后端分离)

    前言 在Postman中可以编写和执行自动化测试,使用 JavaScript 编写基本的 API 测试,自由编写任何用于自动化测试的测试方案. 在POSTMAN中读取Cookie值 1. 我们需要向& ...

  9. 学习Spring Boot:(十二)Mybatis 中自定义枚举转换器

    前言 在 Spring Boot 中使用 Mybatis 中遇到了字段为枚举类型,数据库存储的是枚举的值,发现它不能自动装载. 解决 内置枚举转换器 MyBatis内置了两个枚举转换器分别是:org. ...

随机推荐

  1. python_异常处理_断言

    一.Python标准异常 常用异常 Exception 常规错误的基类 AttributeError 试图访问一个对象没有的属性 IOError 输入/ 输出异常,基本上是无法打开文件 ImportE ...

  2. Python_python内置加密模块

    数据加密: 对称加密:数据加密和解密使用相同的密钥,主要解决数据的机密性(DES,AES) 非对称加密(公匙加密):数据加密和解密使用的不同密钥,主要用于身份的验证(DSA,RSA) 单向加密:只能加 ...

  3. You have new mail in /var/spool/mail/root消除提示的方法

    有时在进入系统的时候经常提示You have new mail in /var/spool/mail/root 你觉得烦人---解决方法: 修改系统配置文件/etc/profile,告诉系统不要去检查 ...

  4. Codeforces 1140G Double Tree 倍增 + dp

    刚开始, 我以为两个点肯定是通过树上最短路径过去的, 无非是在两棵树之间来回切换, 这个可以用倍增 + dp 去维护它. 但是后来又发现, 它可以不通过树上最短路径过去, 我们考虑这样一种情况, 起点 ...

  5. systemd创建自定义服务(Ubuntu)

    /lib/systemd/system下创建test.service文件 vim /lib/systemd/system/test.service [Unit] Description=test [S ...

  6. spring框架等web程序在tomcat下的启动顺序

    http://www.cnblogs.com/panxuejun/p/5847774.html

  7. 用 threading 写多线程服务器

    import socket import threading server = socket.socket() server.bind(("127.0.0.1",8899)) se ...

  8. Aladdin and the Flying Carpet(唯一分解定理)

    题目大意:给两个数a,b,求满足c*d==a且c>=b且d>=b的c,d二元组对数,(c,d)和(d,c)属于同一种情况: 题目分析:根据唯一分解定理,先将a唯一分解,则a的所有正约数的个 ...

  9. css3和html5

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. JAVA项目中常用的异常处理情况

    1.数学运算异常( java.lang.arithmeticexception) 程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不 ...