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);