Jackson で ObjectMapper による JSON 読込で、JSONキーに対してクラス側にフィールドが存在しないと
UnrecognizedPropertyException
を発生するわけだが、それを無視して読込みさせたい時は、
@JsonIgnoreProperties クラスアノテーションで、ignoreUnknown=true を指定する
@JsonIgnoreProperties(ignoreUnknown=true) public class Foo{
instanceof 演算子で if文を書くのが嫌な時、
Class の isInstance メソッドで制御したOptional変数で処理する方法、、、
単に cast する。
Food f = Optional.of(pasta) .filter(e->Food.class.isInstance(e)) .map(e->Food.class.cast(e)) .orElse(null);
cast 成功なら処理する
Optional.of(pasta) .filter(e->Food.class.isInstance(e)) .map(e->Food.class.cast(e)) .ifPresent(e->{ // TODO cast済の e の処理 });
cast できる時とできない時の処理を書く。。
Java8 用
Optional.of(object) .filter(e->Food.class.isInstance(e)) .map(e->Food.class.cast(e)) .<Runnable>map(e->()->{ // TODO cast済の e の処理 }).orElse(()->{ // TODO cast不可能の処理 }).run();
Java10~
Optional.of(object) .filter(e->Foo.class.isInstance(e)) .map(e->Foo.class.cast(e)) .ifPresentOrElse(e->{ // TODO cast済の e の処理 }, ()->{ // TODO cast不可能の処理 });
cast 結果を Optional として返すFunction を戻り値にした static メソッド
ClassCastException 発生時の処理を書けるようにしたメソッド
static <T, R> Function<T, Optional<R>> castFunction(Class<R> cls, Consumer<ClassCastException> cs){ return (T t)->{ try{ return Optional.of(cls.cast(t)); }catch(ClassCastException e) { cs.accept(e); return Optional.empty(); } }; }
Optional<Food> f = castFunction(Food.class, x->{}).apply(pasta);
instanceof 演算子はコンパイル時にチェックしてくれる、むしろチェックしてしまうのではなく
あくまでも実行時に cast したい時の記述として、上記を考えた。
Function<T, R> の andThen , compose は、
以前から、最初の apply 結果が null だったら、
NullPointerException になってしまう不満がありました。
よくありがちなデータ構造
import lombok.Data; @Data public class Bucket{ private Drink drink; }
import lombok.Data; @Data public class Drink{ private String name; }
の時、、
Function<Bucket, Drink> f1 = Bucket::getDrink; Function<Drink, String> f2 = Drink::getName;
Bucketのインスタンス bucket に対して andThenを使えば、
String name = f1.andThen(f2).apply(bucket);
compose を使うなら、
String name = f2.compose(f1).apply(bucket);
であるが、Bucket の Drink が null であれば、NullPointerException になってしまう。
という不満です。
単に、Function#apply の連結実行なら、下記のようなメソッドでも良いが、
これはあまりにもセンスがない
static <T,V,R> R getChain(T t,Function<T,V> before, Function<V,R> after) { return Optional.ofNullable(before.apply(t)).map(after::apply).orElse(null); }
そこで考えました。
Function<T, R> を拡張して andThen , compose をオーバーライドして、
最初の apply 結果が、null の時の対処をしてあげます。
以下、
・andThen , compose オーバーライドでは、最初の apply 結果=null なら、
null を返す Function ラムダ式を返す。
・引数追加の andThen , compose は、最初の apply 結果=null なら、
最終的に求めたい型の値を指定するか、Supplier で取得させるように指定します。
import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; /** * NullableFunction */ public interface NullableFunction<T, R> extends Function<T, R>{ @Override default <V> Function<T, V> andThen(Function<? super R, ? extends V> after){ Objects.requireNonNull(after); return (T t) -> Optional.ofNullable(apply(t)).map(after::apply).orElse(null); } default <V> Function<T, V> andThen(Function<? super R, V> after, V v){ Objects.requireNonNull(after); return (T t) -> Optional.ofNullable(apply(t)).map(after::apply).orElse(v); } default <V> Function<T, V> andThen(Function<? super R, V> after, Supplier<V> sup){ Objects.requireNonNull(after); return (T t) -> Optional.ofNullable(apply(t)).map(after::apply).orElse(sup.get()); } @Override default <V> Function<V, R> compose(Function<? super V, ? extends T> before){ Objects.requireNonNull(before); return (V v) -> Optional.ofNullable(before.apply(v)).map(e->apply(e)).orElse(null); } default <V> Function<V, R> compose(Function<? super V, ? extends T> before, R r){ Objects.requireNonNull(before); return (V v) -> Optional.ofNullable(before.apply(v)).map(e->apply(e)).orElse(r); } default <V> Function<V, R> compose(Function<? super V, ? extends T> before, Supplier<R> sup){ Objects.requireNonNull(before); return (V v) -> Optional.ofNullable(before.apply(v)).map(e->apply(e)).orElse(sup.get()); } static <T,V,R> Function<T, R> bind(Function<T,V> before, Function<V,R> after){ return (T t) -> Optional.ofNullable(before.apply(t)).map(after::apply).orElse(null); } static <T,V,R> Function<T, R> bind(Function<T,V> before, Function<V,R> after, R r){ return (T t) -> Optional.ofNullable(before.apply(t)).map(after::apply).orElse(r); } static <T,V,R> Function<T, R> bind(Function<T,V> before, Function<V,R> after, Supplier<R> sup){ return (T t) -> Optional.ofNullable(before.apply(t)).map(after::apply).orElse(sup.get()); } }
andThen , compose のメソッドが増えてしまいましたが、いろんな場面を考慮すると便利です。
さらに、2つの Functionを指定して実行させる Function をstatic メソッドとして提供します。
サンプル
NullableFunction<Bucket, Drink> f1 = Bucket::getDrink;
String drinkName = f1.andThen(Drink::getName, ()->"Drink name is null").apply(bucket);
となります。
yipuran-mybatis
GitHub - yipuran/yipuran-mybatis: mybatis used application
をリリースやり直した。
enum のタイプハンドラを書いたのだが、
コード値を抱え持ち、コード値を参照するインターフェースに
static メソッドを書いていたのだが、enum 実装として膨れ上がってしまいあまりよくない。
よって、staticメソッドを廃止して、バージョンは、4.6 にした。
enum のインターフェースは、以下のとおり簡単にする。
import java.io.Serializable; /** * Generic enum interface. */ public interface EnumBase<E extends Enum<E>> extends Serializable { /** * コード値を返却. * @return コード値 */ public Object getValue(); }
これによりタイプハンドラも、書き直した。
使い方は変わらない。
import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.yipuran.util.EnumBase; /** * コード値内包 enum タイプハンドラ. * @param <E> */ public class EnumValueTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E>{ private Class<EnumBase<E>> cls; /** * コンストラクタ. * @param cls enumクラス */ public EnumValueTypeHandler(Class<EnumBase<E>> cls) { this.cls = 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()); } } @SuppressWarnings("unchecked") @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException{ try { Object o = rs.getObject(columnName); return (E) Arrays.stream(cls.getEnumConstants()).filter(e->e.getValue().equals(o)).findAny().orElse(null); }catch(SQLException e) { throw e; } } @SuppressWarnings("unchecked") @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException{ try { Object o = rs.getObject(columnIndex); return (E) Arrays.stream(cls.getEnumConstants()).filter(e->e.getValue().equals(o)).findAny().orElse(null); }catch(SQLException e) { throw e; } } @SuppressWarnings("unchecked") @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException{ try { Object o = cs.getObject(columnIndex); return (E) Arrays.stream(cls.getEnumConstants()).filter(e->e.getValue().equals(o)).findAny().orElse(null); }catch(SQLException e) { throw e; } } }
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);
Java の enum 使用でよく列挙名と共にコード値を定義して使用したりする設計は
常套手段の1つである。
public enum Frame { Gold(101), Silver(101), Bronz(102); private int value; private Frame(int value) { this.value = value; } public int getValue(){ return value; } }
よくあるパターンである。
列挙型の並び、( 0始まり、つまり序数=0)は、ordinal() で取得するが、上のように
抱えるコード値取得メソッドは各 enum定義によるもので普遍的ではない。
汎用的な enumタイプハンドラを作ろうと思ってもこのままではできない。
そこで、コード値の取得メソッドを縛ったインターフェースを用意をする。
また、使いそうなメソッドも用意する。
enum で抱えるコード値のが、int でも String でも許容させるので、
Object getValue(); で取得するものとしている。
循環参照する総称型である。
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); } }
使用例
public enum Frame implements EnumBase<Frame>{ Gold(100), Silver(101), Bronz(102); private int value; private Frame(int value) { this.value = value; } @Override public Object getValue(){ return value; } }
System.out.println("Frame.Gold getCode() : " + Frame.Gold.getValue() ); // enum values() と同じ結果を求める Frame[] frames = EnumBase.getArray(Frame.class); System.out.println("--- Arrays.stream(frames).forEach(System.out::println); ---"); Arrays.stream(frames).forEach(System.out::println); System.out.println("---------"); Frame g = Frame.valueOf("Gold"); System.out.println( g +" "+g.getValue() ); System.out.println("--- Frame v = EnumBase.valueOf(Frame.class, \"Silver\"); ---"); Frame v = EnumBase.valueOf(Frame.class, "Silver"); Frame v2 = EnumBase.parseOf(Frame.class, "Silver").orElse(null); System.out.println("EnumBase<Frame> v : "+v.name() ); System.out.println("v.equlas(v2) : "+v.equals(v2) ); Optional<Frame> v102 = EnumBase.parseCode(Frame.class, 102); System.out.println("EnumBase<Frame> v102 : "+v102.map(o ->o.name()).orElse(null) ); Frame b = EnumBase.codeOf(Frame.class, 101); System.out.println(b.name());
実行して標準出力されるもの。。。
Frame.Gold getCode() : 100 --- Arrays.stream(frames).forEach(System.out::println); --- Gold Silver Bronz --------- Gold 100 --- Frame v = EnumBase.valueOf(Frame.class, "Silver"); --- EnumBase<Frame> v : Silver v.equlas(v2) : true EnumBase<Frame> v102 : Bronz Silver
この EnumBase を使って enum用タイプハンドラは、
https://oboe2uran.hatenablog.com/entry/2021/03/14/003000
MySQL 5.7 あたりは、JDBC ドライバ指定は、
com.mysql.jdbc.Driver
だったけど、
以下、Maven で取得したコネクターを使うなら、
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependen
ドライバは、
com.mysql.cj.jdbc.Driver
を指定してやらないと、、
Loading class `com.mysql.jdbc.Driver'. This is deprecated.
The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI
and manual loading of the driver class is generally unnecessary.
と怒られた
DB接続URLで最後に、serverTimezone=JST を付与したら怒られた