From: https://segmentfault.com/a/1190000010755321

问题

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

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; }@Overridepublic 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类型的参数转换为对应的数据库类型

  1. T getNullableResult(ResultSet rs, String columnName)

用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型

  1. T getNullableResult(ResultSet rs, int columnIndex)

用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型

  1. 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;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BaseCodeEnum parameter, JdbcType jdbcType)throws SQLException {ps.setInt(i, parameter.getCode());}@Overridepublic E getNullableResult(ResultSet rs, String columnName) throws SQLException {int code = rs.getInt(columnName);return rs.wasNull() ? null : codeOf(code);}@Overridepublic E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {int code = rs.getInt(columnIndex);return rs.wasNull() ? null : codeOf(code);}@Overridepublic E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {int code = cs.getInt(columnIndex);return cs.wasNull() ? null : codeOf(code);}private E codeOf(int code){try {return CodeEnumUtil.codeOf(type, code);} catch (Exception ex) {throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code 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. 再写一个能自动匹配转换行为的转换器
  2. 通过sqlSessionFactory.getConfiguration().getTypeHandlerRegistry()取得类型转换器注册器
  3. 再使用typeHandlerRegistry.setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler)将第一步的转换器注册成为默认的

首先,我们需要一个能确定转换行为的转换器:
AutoEnumTypeHandler.java


public class AutoEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {private BaseTypeHandler typeHandler = null;public AutoEnumTypeHandler(Class<E> type) {if (type == null) {throw new IllegalArgumentException("Type argument cannot be null");}if(BaseCodeEnum.class.isAssignableFrom(type)){// 如果实现了 BaseCodeEnum 则使用我们自定义的转换器typeHandler = new CodeEnumTypeHandler(type);}else {// 默认转换器 也可换成 EnumOrdinalTypeHandlertypeHandler = new EnumTypeHandler<>(type);}}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {typeHandler.setNonNullParameter(ps,i, parameter,jdbcType);}@Overridepublic E getNullableResult(ResultSet rs, String columnName) throws SQLException {return (E) typeHandler.getNullableResult(rs,columnName);}@Overridepublic E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return (E) typeHandler.getNullableResult(rs,columnIndex);}@Overridepublic E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return (E) typeHandler.getNullableResult(cs,columnIndex);}
}

接下来,我们需要干预SqlSessionFactory的创建过程,将刚刚的转换器指定为默认的:

@Configuration
@ConfigurationProperties(prefix = "mybatis")
public class MyBatisConfig {private String configLocation;private String mapperLocations;@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource,JSONArrayHandler jsonArrayHandler,JSONObjectHandler jsonObjectHandler) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();factory.setDataSource(dataSource);// 设置配置文件及mapper文件地址ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();factory.setConfigLocation(resolver.getResource(configLocation));factory.setMapperLocations(resolver.getResources(mapperLocations));SqlSessionFactory sqlSessionFactory = factory.getObject();// 取得类型转换注册器TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();// 注册默认枚举转换器typeHandlerRegistry.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);return sqlSessionFactory;}// ... getter setter
}

搞定! 这样一来,如果枚举实现了BaseCodeEnum接口就使用我们自定义的CodeEnumTypeHandler,如果没有实现BaseCodeEnum接口就使用默认的。再也不用写MyBatis的配置文件了!

结束了

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

如何在MyBatis中优雅的使用枚举相关推荐

  1. easyswoole数据库连接池_如何在 Swoole 中优雅的实现 MySQL 连接池

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

  2. 如何在vscode中优雅的编写C语言

    如何在vscode中优雅的编写C语言 各位好,我认为vscode编辑器在windows环境下除了Pycharm外是最方便的IDE了,但在初学C语言时很少有人的第一个C语言软件使用的是vscode来编译 ...

  3. boost::unorder_map如何插入元素_「React」如何在React中优雅的实现动画

    最简单的动画组件实现 动画的本质,无非就是一个状态样式到另一个状态样式的过渡.最简单的动画组件,我们只需要指定两个状态的样式(进入的样式,离开的样式),以及一个开关(控制状态),即可完成. codep ...

  4. 如何在Django中优雅的使用pyecharts设计可视化BI系统(多图表)

    这两天琢磨了一下pyecharts这个库,自己总结了一些内容,具体如下: 大多数人在做用python库做数据分析的时候,都在用jupyter,用这个工具是没有错,而且非常方便就可以即时的显示数据,这个 ...

  5. kylin版本_如何在 Kylin 中优雅地使用 Spark

    前言 Kylin 用户在使用 Spark的过程中,经常会遇到任务提交缓慢.构建节点不稳定的问题.为了更方便地向 Spark 提交.管理和监控任务,有些用户会使用 Livy 作为 Spark 的交互接口 ...

  6. 如何在 Kylin 中优雅地使用 Spark

    前言 Kylin 用户在使用 Spark的过程中,经常会遇到任务提交缓慢.构建节点不稳定的问题.为了更方便地向 Spark 提交.管理和监控任务,有些用户会使用 Livy 作为 Spark 的交互接口 ...

  7. Mendeley+LaTex: 如何在Latex中优雅的插入引用文献

    结合Mendeley在LaTex中插入并引用参考文献十分方便与便捷. 步骤如下: 1)在Mendeley中导入文献 2)选中文献,鼠标右键-->Update Details Mendeley的好 ...

  8. 如何在vue中优雅的使用ocx控件:控件引用

    最近刚好在做一个自助机项目,限于个人技术栈,也是为了后期更新维护方便,采用了BS的形式来开发. 自助机需要控制摄像头.身份证读卡器.扫描仪.手写签名等硬件,目前只有IE的ocx控件可以提供比较好的支持 ...

  9. 如何在DataFrame 中优雅的增加一行,一列

    <font color='darkgreen',size=4.5>    优雅的增加一行,一定要优雅! df=DataFrame(np.arange(16).reshape((4,4)), ...

最新文章

  1. 美团Serverless产品落地与演进
  2. 变频器显示5cf1是什么意思_空调显示e0什么意思
  3. ⚡如何在2分钟内将GraphQL服务器添加到RESTful Express.js API
  4. python 可执行文件打包_使用可执行文件打包Python库
  5. python网络编程2-黏包问题
  6. 电力笔记-30个行业专业词汇(Ⅰ期)
  7. mysql差异备份数据库get shell_shell进行完整和增量备份mysql数据库
  8. java读写excel文件
  9. 数据结构与算法:十大排序算法之堆排序
  10. MFC中如何画带实心箭头的直线
  11. C# 连接Access数据库
  12. ISO27000系列标准
  13. FOXIT PDF EDITOR工具分割PDF
  14. 使用tf2的saved_model进行推理
  15. 1196踩方格—递推方法!
  16. jquery判断是否按下Enter(回车)和TAB键
  17. 牛客-Mysql实战-按热度排序(前20)
  18. 南宁供电局抄表及电量电费管理系统的开发设计
  19. Jenkins启动报错:Jenkins requires Java versions [8, 11] but you are running with Java 13 from xx/xx/xx
  20. 物联网设备数据流转之实时数据从哪里来、如何转发:Node.js, MQTT, EMQX的WebHook

热门文章

  1. Cisco asa 5520 oid
  2. 我们应当学会辞旧迎新——《不一样的天空》
  3. leetcode1438. 绝对差不超过限制的最长连续子数组
  4. leetcode1282. 用户分组(贪心算法)
  5. django构建网页_如何使用Django构建照片供稿
  6. 永无止境_永无止境地死:
  7. 多元时间序列回归模型_多元时间序列分析和预测:将向量自回归(VAR)模型应用于实际的多元数据集...
  8. SQL mysql优化
  9. iostat -x命令诊断
  10. 数据库和数据挖掘领域的会议和期刊