mybatis の enum TypeHandler を汎用的にする。

mybatis が enum に対するハンドラは、列挙名だけのマッピングなら、
   org.apache.ibatis.type.EnumTypeHandler
列挙の並び、enum の int ordinal() メソッド、(0始まり、序数=0)で満足なら
   org.apache.ibatis.type.EnumOrdinalTypeHandler
をで良いのだが、enum が抱えるコード値が、0始まりでない場合は、
mybatis の BaseTypeHandler を継承しなくてはならない。
以下のように仕方なく書いていたであろう。
 (まずは、汎用化のコード紹介の前に、仕方がないコードの現状)

public enum Color {
   Red(11), Green(12), Blue(13);

   private int id;
   private Color(int id) {
      this.id = id;
   }
   public int getValue(){
      return id;
   }
}

これのハンドラクラス、、、

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.labo.dbtest.entity.Color;
import org.labo.foo.tools.EnumBase;
/**
 * ColorHandler
 */
public class ColorHandler extends BaseTypeHandler<Color>{
   @Override
   public void setNonNullParameter(PreparedStatement ps, int i, Color parameter
   , JdbcType jdbcType) throws SQLException{
      if (parameter==null){
         ps.setObject(i, null); 
     }else{
          ps.setObject(i, parameter.getValue());
      }
   }
   @Override
   public Color getNullableResult(ResultSet rs, String columnName) throws SQLException{
      int i = rs.getInt(columnName);
      return Arrays.stream(Color.values()).filter(e->e.getValue()==i).findAny().orElse(null);
   }
   @Override
   public Color getNullableResult(ResultSet rs, int columnIndex) throws SQLException{
      int i = rs.getInt(columnIndex);
      return Arrays.stream(Color.values()).filter(e->e.getValue()==i).findAny().orElse(null);
   }
   @Override
   public Color getNullableResult(CallableStatement cs, int columnIndex) throws SQLException{
      int i = rs.getInt(columnIndex);
      return Arrays.stream(Color.values()).filter(e->e.getValue()==i).findAny().orElse(null);
   }
}

mybatis の接続 Config のXML で、、、タイプハンドラを指定

<typeHandlers>
  <typeHandler handler="org.uranus.types.ColorHandler" javaType="org.uranus.data.Color"/>
</typeHandlers>

mybatos 接続を config XML で指定しない場合、、

Configuration config = new Configuration(environment);
// snake Case → camel Case
config.setMapUnderscoreToCamelCase(true);
config.addMapper(mapperclass);

config.getTypeHandlerRegistry().register(Color.class, ColorHandler.class );

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);

enum 定義の管理と、mybatis 接続管理で面倒。。。
ここから、本題、、、
enum 定義で約束させるインターフェースを用意する。

import java.util.Arrays;
import java.util.Optional;
/**
 * Generic enum Interface
 */
public interface EnumBase<E extends Enum<E>>{

   public Object getValue();

   static <E> E[] getArray(Class<E> clz) {
      return clz.getEnumConstants();
   }

   @SuppressWarnings("unchecked")
   static <E extends Enum<E>> E valueOf(Class<? extends  Enum<E>> cls, String value) {
      return (E)Arrays.stream(cls.getEnumConstants())
            .filter(e->e.name().equals(value))
            .findAny()
            .orElse(null);
   }

   @SuppressWarnings("unchecked")
   static <E extends Enum<E>> Optional<E> parseOf(Class<? extends  Enum<E>> cls, String value) {
      return (Optional<E>) Arrays.stream(cls.getEnumConstants())
            .filter(e->e.name().equals(value))
            .findAny();
   }

   @SuppressWarnings("unchecked")
   static <E extends Enum<E>> Optional<E> parseCode(Class<? extends  EnumBase<E>> cls, Object code) {
      return (Optional<E>) Arrays.stream(cls.getEnumConstants())
            .filter(e->e.getValue().equals(code))
            .findAny();
   }

   @SuppressWarnings("unchecked")
   static <E extends Enum<E>> E codeOf(Class<? extends  EnumBase<E>> cls, Object code) {
      return (E) Arrays.stream(cls.getEnumConstants())
            .filter(e->e.getValue().equals(code))
            .findAny().orElse(null);
   }
}

enum もこれに合わせる。getValue() で Object として返すようにする。

public enum Color implements EnumBase<Color> {
   Red(11), Green(12), Blue(13);

   private int id;
   private Color(int id) {
      this.id = id;
   }
   @Override
   public Object getValue(){
      return id;
   }
}

enum と結びつけるハンドラは、常に以下を使う。。。

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.labo.foo.tools.EnumBase;

public class EnumValueTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E>{
   private Class<EnumBase<E>> cls;
   @SuppressWarnings("unchecked")
   public EnumValueTypeHandler(Class<E> cls) {
      this.cls = (Class<EnumBase<E>>) cls;
   }

   @SuppressWarnings("unchecked")
   @Override
   public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException{
      if (parameter==null) {
         ps.setObject(i, null);
      }else{
         ps.setObject(i, ((EnumBase<E>)parameter).getValue());
      }
   }

   @Override
   public E getNullableResult(ResultSet rs, String columnName) throws SQLException{
      return EnumBase.parseCode(cls, rs.getInt(columnName)).orElse(null);
   }
   @Override
   public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException{
      return EnumBase.parseCode(cls, rs.getInt(columnIndex)).orElse(null);
   }
   @Override
   public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException{
      return EnumBase.parseCode(cls, cs.getInt(columnIndex)).orElse(null);
   }
}

この EnumValueTypeHandler を指定する。。

<typeHandlers>
  <typeHandler handler="org.uranus.types.EnumValueTypeHandler" javaType="org.uranus.data.Color"/>
</typeHandlers>

mybatos 接続を config XML で指定しない場合、、

Configuration config = new Configuration(environment);
// snake Case → camel Case
config.setMapUnderscoreToCamelCase(true);
config.addMapper(mapperclass);

config.getTypeHandlerRegistry().register(Color.class, EnumValueTypeHandler.class );

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);

これで完了

注意しなければならないのが、config XML で指定しない場合に限り、、、
getTypeHandlerRegistry().register( ) で指定する場合、、、
@Insert で実行する記述だけ、以下のように enum タイプハンドラを指定しないとならない。
他の @Select などは、共通一括のタイプハンドラの指定、SQLMap XML で select でも
個々に書く必要はないのだが、
なぜか、このconfig XML で指定しない場合の
getTypeHandlerRegistry().register( )
では、
@Insert に限り、、、
以下のように、typeHandler指定の@Insert を書かなくてはならなかった。

@Insert("INSERT INTO tblitem(itemname, color)VALUES( #{itemname}" 
 + ",#{color, typeHandler=org.uranus.types.EnumValueTypeHandler} )")
public void insertItem(Item item);