欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MyBatis中如何优雅的使用枚举详解

程序员文章站 2024-02-21 10:53:40
问题 本文主要给大家介绍的是关于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.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类用于我们自己扩展类型转换器,上面的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中优雅的使用枚举的探索。如果你还有更优的解决方案,请一定在评论中告知,万分感激。希望本文的内容对大家的学习或者工作能带来一定的帮助,谢谢大家对的支持。