MyBatis中如何优雅的使用枚举详解
问题
本文主要给大家介绍的是关于mybatis使用枚举的相关内容,我们在编码过程中,经常会遇到用某个数值来表示某种状态、类型或者阶段的情况,比如有这样一个枚举:
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.enumtypehandler
和org.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
类用于我们自己扩展类型转换器,上面的enumtypehandler和enumordinaltypehandler也都实现了这个接口。
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个方法:
-
void setnonnullparameter(preparedstatement ps, int i, t parameter, jdbctype jdbctype)
用于定义设置参数时,该如何把java类型的参数转换为对应的数据库类型 -
t getnullableresult(resultset rs, string columnname)
用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的java类型 -
t getnullableresult(resultset rs, int columnindex)
用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的java类型 -
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 boot中我们可以干预sqlsessionfactory的创建过程,来完成动态的转换器指定。
思路
- 通过
sqlsessionfactory.getconfiguration().gettypehandlerregistry()
取得类型转换器注册器 - 扫描所有实体类,找到实现了basecodeenum接口的枚举类
- 将实现了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中优雅的使用枚举的探索。如果你还有更优的解决方案,请一定在评论中告知,万分感激。希望本文的内容对大家的学习或者工作能带来一定的帮助,谢谢大家对的支持。
上一篇: java多线程学习笔记之自定义线程池
下一篇: Jdbc连Sybase数据库的几种方法