问题

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

 
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. window.open实现模式窗口

    看了些文章,实现模式窗口有两种方式.window.showModalDialog以及window.open. 一.方式介绍 window.open()支持环境: JavaScript1.0+/JScr ...

  2. redis 配置命令

    Redis:是一个key/v  型数据 是nosql的一种 CAP 理论: C:多个数据节点上的数据一致: A:用户发出请求后的有限时间范围内返回结果: P:network partition,网络发 ...

  3. ELK 环境搭建2-Kibana

    一.安装前准备 1.节点 192.168.30.41 2.操作系统: Centos7.5 3.安装包 a.java8: jdk-8u181-linux-x64.tar.gz b.Kibana kiba ...

  4. Python自用笔记

    函数:raw_input()和input() 注意:在python3.x中,已经删除raw_input(),取而代之的是input(),当然这仅仅是重命名,用法还是一样.因此在这里介绍的是python ...

  5. Hadoop |集群的搭建

    Hadoop组成 HDFS(Hadoop Distributed File System)架构概述 NameNode目录--主刀医生(nn):  DataNode(dn)数据: Secondary N ...

  6. Git branch 出现"HEAD detached at head xxxxx"

    Git branch 出现"HEAD detached at head xxxxx" git branch <your-branch-name> xxxxx      ...

  7. HDU 1533 Going Home (最大权完美匹配)

    <题目链接> 题目大意:给你一张地图,地图上m代表人,H代表房子,现在所有人要走到房子内,且一个房子只能容纳一个人(人和房子的数量相同),人每移动一步,需要花1美元,问所有人走到房子中的最 ...

  8. python之迭代器与生成器

    python之迭代器与生成器 可迭代 假如现在有一个列表,有一个int类型的12345.我们循环输出. list=[1,2,3,4,5] for i in list: print(i) for i i ...

  9. BZOJ-4-2038: [2009国家集训队]小Z的袜子(hose)-莫队

    思路 :分块 思想 处理离线查询操作  对查询进行排序 在同一块内的按照 r 进行排序 不同块 的按照 L进行排序. #include<bits/stdc++.h> using names ...

  10. url两次编码

    encodeURI函数采用UTF-8对URL进行编码,所以如果服务器在进行解码时使用的是其他的编码方式就会出现乱码,默认的服务器配置的解码字符集都不是UTF-8,所以大部分情况下地址栏提交中文查询参数 ...