mybatis logbackが出なくなった場合の対処

mybatis 、logback を使っていて、logback のバージョン上げたのか何かのきっかけ、
依存関係かなにか?指定しているアペンダ悪い?で mybatis のデバッグレベルのログが出なくなってしまった。
実行するSQL文が見れてデバッグに便利だった

logaback.xml で書くロガー設定で、mybatis のSQLMapperのネームスペースで指定すると
強制的に出すことができる。

mybatis の SQLMap XMLで、

  <mapper namespace="jp.xxx.sql.SimpleMapper">

としていた場合、

logback.xml で以下のようにすれば、この SQLMapper で実行するSQL文をデバッグログとして出力できる。

<logger name="jp.xxx.sql.SimpleMapper" additivity="true">
   <level value="debug" />
   <appender-ref ref="FILE" />
</logger>

additivity 属性が問題で、additivity="false" は、親のロガーで設定されたアペンダを継承しない。
という指定で、以前の変更までまで出ていたSQL文を出したいので、true を指定する。

フェードイン、フェードアウトするメッセージ(改編)

昔、フェードイン、フェードアウトするメッセージの jQuery を書いた。
見直すと中央寄せが中途半端でできていない。書き直す。

CSSソース

@CHARSET "UTF-8";
/**
 * fadeinmsg.css
 */
div#fadeMessageModal{
   display: none;
   position: fixed;
   width: 100%;
   height: 100%;
   z-index: 300001!important;
}
div#fadeMessageModal div.background{
   position: fixed;
   width: 100%;
   height: 100%;
   background-color: #000000;
   opacity: 0.25;
   filter: alpha(opacity=25);
   -ms-filter: "alpha(opacity=25)";
   z-index: 300001;
}
div#fadeMessageModal div.container{
   width: auto;
   box-shadow:0px 0px 40px;
   background-color: #fafad2;
   padding: 12px 20px;
   border-radius: 16px;
   position: absolute;
   top: 50%;
   left: 50%;
   -webkit-transform: translateY(-50%) translateX(-50%);
   transform: translateY(-50%) translateX(-50%);
   font-size: 24px;
   color: #000000;
   margin: auto auto;
   z-index: 300001;
   white-space: nowrap;
}

以下が抜けてた。

-webkit-transform: translateY(-50%) translateX(-50%);
transform: translateY(-50%) translateX(-50%);

jQuery のソース

/**
 * fadeinmsg.js
 *
 * showFadeMessage(message , [options])
 *
 * @param message メッセージ文字列
 * @param option オプション(フェードイン時間等)
 * options = {  fadein   : フェードイン時間、ミリ秒
 *            , interval : 表示時間、フェードアウト開始までの時間、ミリ秒
 *            , fadeout  : フェードアウト時間、ミリ秒
 *            , fontsize : 文字サイズ、font-size
 *           }
 */
var showFadeMessage = function(msg, options){
   var intime   = 3000;
   var interval = 2000;
   var outtime  = 3000;
   var fontoption  = "";
   if (!$.isEmptyObject(options)){
      if (options['fadein'] != undefined){
         intime = options['fadein'];
      }
      if (options['interval'] != undefined){
         interval = options['interval'];
      }
      if (options['fadeout'] != undefined){
         outtime = options['fadeout'];
      }
      if (options['fontsize'] != undefined){
         fontoption = 'style="font-size: ' + options['fontsize'] + ';"';
      }
   }
   $('body').prepend('<div id="fadeMessageModal"><div class="background"></div><div id="fadeMessage" class="container" ' + fontoption + '></div></div>');
   $('#fadeMessage').html(msg);
   $('#fadeMessageModal').fadeIn(intime).delay(interval).fadeOut(outtime, function(){
      $('#fadeMessageModal').remove();
   });
};

使うサンプル

<script type="text/javascript">
$(function(){
   $("button").click(function(){
     showFadeMessage('<strong style="color:#ff0000;">警告!</strong>メッセージ<br/>ABCD', {fadein:100, interval:10000, fadeout:2000});
   });
});
</script>

f:id:posturan:20170916225907j:plain

Optional の filter を連結して使う

Webアプリでのフォーム入力に限らす、Javaでは変数への入力実行の後の妥当性チェックを記述していると
どうしてもコードが長くなる。
コードが長いと、タイプミスの確率も上がるし、なにしろ読むのが辛い。長くても何回も if文を書きたくない。

java.util.Optional を単純に使うので連結を考えてみる。入力規則外を例外捕捉で処理する考え方。。。

例)日付の入力、"/" 区切りの日付文字列

String datestring = "2017/09/31";

try{
   LocalDate dt =   Optional.ofNullable(datestring)
   .filter(e->Pattern.compile("^\\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])$").matcher(e).matches())
   .filter(e->{
      DateFormat format = new SimpleDateFormat("yyyy/MM/dd");
      format.setLenient(false);
      try{
         format.parse(e);
         return true;
      }catch(ParseException ex){
         throw new RuntimeException(ex);
      }
   })
   .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
   .orElseThrow(()->new IllegalArgumentException("Format Error"));

   // 正常時の処理

}catch(Exception ex){
   if(ex instanceof IllegalArgumentException){
      // 書式エラー、null の場合を捕捉
   }
   if (ex.getCause() instanceof ParseException){
      // 存在できない日付の場合を捕捉
   }
   ex.printStackTrace();
}

古いJavajava.text.DateFormat 、 java.text.SimpleDateFormat を使って
ありえない日付のチェックをしている。ここをスマートに書きたいのだがどうにもならない。
上のコードは、null も、nullでない書式エラーも、IllegalArgumentException で捕捉する。
setLenient を実行した format で parse して発生する ParseException ラムダで飛び越えられないので
RuntimeExceptionでラップする。

せめて、ありえない日付のチェックを外にメソッドにする。

public static boolean checkDate(String strDate, String pattern){
   DateFormat format = new SimpleDateFormat(pattern);
   format.setLenient(false);
   try{
      format.parse(strDate);
      return true;
   }catch(ParseException e){
      return false;
   }
}

書き直す。

String datestring = "2017/09/31";

try{
   LocalDate dt =   Optional.ofNullable(datestring)
   .filter(e->Pattern.compile("^\\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])$").matcher(e).matches())
   .filter(e->checkDate(e, "yyyy/MM/dd"))
   .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
   .orElseThrow(()->new IllegalArgumentException("Format Error"));

   // 正常時の処理

}catch(Exception ex){
   if(ex instanceof IllegalArgumentException){
      // 書式エラー、null の場合を捕捉
   }
   if (ex.getCause() instanceof ParseException){
      // 存在できない日付の場合を捕捉
   }
   ex.printStackTrace();
}

少しは綺麗になる。

時刻正規表現

24時間制、0~23時のHH:mm:ss の正規表現

^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])$

Model.orElseGet(SerializableSupplier<? extends T> other) は使えるかも

Wicket で、IModel インスタンスを書く時 、コーディングの癖で、 new 演算子による
new Model<>() を書くことが多い。
Model.of があるのは知ってはいたけど、なぜかあまり書かなかった。

https://issues.apache.org/jira/browse/WICKET-6412

Model.orElse を書いてフォームの submit でも、結局は問題は起きるようで、
String の TextField を使うときに、使うのが良いかも。

それよりも、Java8 ラムダになって、Model クラスでも、

   IModel<T> orElseGet(SerializableSupplier<? extends T> other)

を書けることの方が魅力的です。

ストアドファンクション生成 Error Code: 1419

他人が構築したDBを引き継ぐのは、嫌なもので状況を把握しないまま、
MySQL ストアドファンクションを追加したら、

Error Code: 1419. You do not have the SUPER privilege and binary logging is
enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

マニュアルには、こう書いてある。

「バイナリロギングを有効にしているサーバーに対するこの危険から保護するために、
ストアドファンクションの作成者は、必要な通常の CREATE ROUTINE 権限に加え、SUPER 権限も持つ必要があります。
同様に、ALTER FUNCTION を使用するには、ユーザーは ALTER ROUTINE 権限に加え、SUPER 権限を持つ必要があります。
SUPER 権限がないと、エラーが発生します。」


しかたないので、権限のある root で入り、log_bin_trust_function_creators を調べる。

mysql> SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| log_bin_trust_function_creators | OFF   |
+---------------------------------+-------+

なので、ONにするために、

mysql> set global log_bin_trust_function_creators=1;
Query OK, 0 rows affected (0.00 sec)

確認、

mysql> SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| log_bin_trust_function_creators | ON    |
+---------------------------------+-------+
1 row in set (0.00 sec)

これで、無事、ストアドファンクション生成できるようになる。

グルーピング&ソートの続き

先日のグルーピング&ソートに続いて、、、
グルーピングした時にソートもする。 - Oboe吹きプログラマの黙示録

同じく Foo なるクラスオブジェクトのリストで、グルーピングして並べた時に、先頭だけキーをそのままで
他をキーが表示されないように置き換えなどの細工をする。

key  :  value
-------------
  A  : 1
     : 2
     : 3
  B  : 11
     : 12
  C  : 31

のように表示できるようにリストを変更したいする。すぐに思いつくのが、groupingBy して作った Map の
entrySet() ストリームを流して、flatMap で、変更する方法、、

List<Foo> results = list.stream().collect(Collectors.groupingBy(t->t.key, Collectors.mapping(u->u, Collectors.toList())))
.entrySet().stream().flatMap(e->{
      AtomicInteger i = new AtomicInteger(0);
      return e.getValue().stream().sorted((o1, o2)->new Integer(o1.value).compareTo(new Integer(o2.value))).map(t->{
         if (i.getAndIncrement() > 0) t.key = " ";
         return t;
      });
   }
).collect(Collectors.toList());

でも、AtomicInteger生成文が入って気持ち悪い。
遅くて効率が悪いかもしれなけど、Stream.concat を使ってみる。

List<Foo> results = list.stream().collect(Collectors.groupingBy(t->t.key, Collectors.mapping(u->u, Collectors.toList())))
.entrySet().stream().flatMap(e->{
   List<Foo> _list = e.getValue().stream()
   .sorted((o1, o2)->new Integer(o1.value).compareTo(new Integer(o2.value))).collect(Collectors.toList());
   return Stream.concat(_list.stream().limit(1)
      , _list.stream().skip(1).map(t->{
         t.key = " ";
         return t;
   }));
}).collect(Collectors.toList());

キーそのものもソートまで実施するなら、こんなんじゃダメなので、

Map<String, List<Foo>> map =
list.stream().collect(Collectors.groupingBy(e->e.key, Collectors.mapping(t->t,
   Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new)
      , t->{
          List<Foo> _list =
          t.stream().sorted((o1, o2)->new Integer(o1.value)
          .compareTo(new Integer(o2.value))).collect(Collectors.toList());
          return Stream.concat(_list.stream().limit(1), _list.stream().skip(1).map(u->{
                u.key = " ";
                return u;
        })).collect(Collectors.toList());
    })
)));

ソートもされるけど、何だか遅そう。。