Jackson でJSON読込み時、フィールド存在しない場合

Jackson で ObjectMapper による JSON 読込で、JSONキーに対してクラス側にフィールドが存在しないと
 UnrecognizedPropertyException
を発生するわけだが、それを無視して読込みさせたい時は、
@JsonIgnoreProperties クラスアノテーションで、ignoreUnknown=true を指定する

@JsonIgnoreProperties(ignoreUnknown=true)
public class Foo{

ClassCastException を配慮した cast 処理

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 における NullPointerException の回避

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;

とあれば、、Bucket bucket に対して

String drinkName = f1.andThen(Drink::getName, ()->"Drink name is null").apply(bucket);

となります。

コード値を抱える enum のタイプハンドラ

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

コンストラクタで値を抱える enum 列挙型の汎用インターフェース

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 8.x からは、JDBCドライバ指定は変えないとダメだった

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 を付与したら怒られた