问题 在编码过程中,经常会遇到用某个数值来表示某种状态、类型或者阶段的情况,比如有这样一个枚举:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 是否能够满足我们的需求。org.apache.ibatis.type.EnumTypeHandler 和 org.apache.ibatis.type.EnumOrdinalTypeHandler。
EnumTypeHandler 这是默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串,即将 ComputerState.OPEN 转换 OPEN。
EnumOrdinalTypeHandler 顾名思义这个转换器将枚举实例的 ordinal 属性作为取值,即 ComputerState.OPEN 转换为 0,ComputerState.CLOSE 转换为 1。
1 <typeHandlers >  <typeHandler  handler ="org.apache.ibatis.type.EnumOrdinalTypeHandler"  javaType ="com.example.entity.enums.ComputerState" />  </typeHandlers > 
  
方案 MyBatis 提供了 org.apache.ibatis.type.BaseTypeHandler 类用于我们自己扩展类型转换器,上面的 EnumTypeHandler 和 EnumOrdinalTypeHandler 也都实现了这个接口。
1. 定义接口 我们需要一个接口来确定某部分枚举类的行为。如下:
1 2 3 public  interface  BaseCodeEnum  {    int  getCode () ; } 
2. 改造枚举 就拿上面的 ComputerState 来实现 BaseCodeEnum 接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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. 编写一个转换工具类 现在我们能顺利的将枚举转换为某个数值了,还需要一个工具将数值转换为枚举实例。
1 2 3 4 5 6 7 8 9 10 11 12 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(ResultSt rs, int columnIndex)T getNullableResult(CallableStatement cs, int columnIndex) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public  class  CodeEnumTypeHandler <E extends  Enum <?> & BaseCodeEnum> extends  BaseTypeHandler  {    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  code  =  rs.getInt(columnName);         return  rs.wasNull() ? null  : codeOf(code);     }          @Override        public  E getNullableResult (ResultSet rs, int  columnIndex)  throws  SQLException {         int  code  =  rs.getInt(columnIndex);         return  rs.wasNull() ? null  : codeOf(code);     }          @Override        public  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 配置文件中配置如下:
1 <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 的配置文件维护起来会变得非常麻烦,也容易出错。如何解决呢?
思路 
再写一个能自动匹配转换行为的转换器 
通过 sqlSessionFactory.getConfiguration().getTypeHandlerRegistry() 取得类型转换器注册器 
再使用 typeHandlerRegistry.setDefaultEnumTypeHandler (Class<? extends TypeHandler> typeHandler) 将第一步的转换器注册成为默认的 
 
首先,我们需要一个能确定转换行为的转换器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public  class  AutoEnumTypeHandler <E extends  Enum > extends  BaseTypeHandler  {         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)){                          typeHandler = new  CodeEnumTypeHandler (type);         }else  {                          typeHandler = new  EnumTypeHandler <>(type);         }     }          @Override        public  void  setNonNullParameter (PreparedStatement ps, int  i, E parameter, JdbcType jdbcType)  throws  SQLException {         typeHandler.setNonNullParameter(ps,i, parameter,jdbcType);     }          @Override        public  E getNullableResult (ResultSet rs, String columnName)  throws  SQLException {         return  (E) typeHandler.getNullableResult(rs,columnName);     }          @Override        public  E getNullableResult (ResultSet rs, int  columnIndex)  throws  SQLException {         return  (E) typeHandler.getNullableResult(rs,columnIndex);     }          @Override        public  E getNullableResult (CallableStatement cs, int  columnIndex)  throws  SQLException {         return  (E) typeHandler.getNullableResult(cs,columnIndex);     }      } 
接下来,我们需要干预 SqlSessionFactory 的创建过程,将刚刚的转换器指定为默认的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Configuration   @ConfigurationProperties(prefix  = "mybatis")  public  class  MyBatisConfig  {    private  String configLocation;     private  String mapperLocations;     @Bean        public  SqlSessionFactory sqlSessionFactory (          DataSource dataSource,         JSONArrayHandler jsonArrayHandler,         JSONObjectHandler jsonObjectHandler)  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();                       TypeHandlerRegistry  typeHandlerRegistry  =  sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();                  typeHandlerRegistry.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);              return  sqlSessionFactory;     }           } 
搞定! 这样一来,如果枚举实现了 BaseCodeEnum 接口就使用我们自定义的 CodeEnumTypeHandler,如果没有实现 BaseCodeEnum 接口就使用默认的。再也不用写 MyBatis 的配置文件了!