utf8mb3に入るようにJavaで制限をかけたい

## やりたいことは簡単なのに。。。

MySqlのvarcharが扱える文字コード（character set）は、デフォルトでutf8であり、これはutf8mb3（３バイト文字）のエイリアスでもあるようです（バージョンがあがるごとにだんだんutf8mb4に移行しつつあるようですが）。
で、業務で使うmysql5.7には、utf8で接続することになっているため、４バイト文字が入りません。
というわけで、サーバー側(java)で文字を切ってあげればよいことになりました。

調べてみたものの、なんかスマートな方法がぱっと出てこなかったので、なんとなく調べたことをまとめておきます。

## サロゲートペアと4バイト文字の違い

まずこれがわからなかった。
上司に言われた指示は「絵文字を使えないようにしたい」だった。
自分の会社の先達が作ったコードでは、バリデーションでサロゲートペアをひっかけており、これを参考にしろと提示された。

で、サロゲートペアって？

## わからんので調べてみる

いったんUnicodeとutf8、サロゲートペアの話をある程度理解してから進みます。

- ある文字があったとき、Unicodeとutf8では、それぞれ違う符号であらわす
    - 通信上はUnicodeで行い、実際プログラムが利用するときにutf8の符号にする
- utf8は1~3バイトで表す
    - データ通信等ではUnicodeを使用し、実際のプログラムでは符号化（1~3バイト）して使う
        - 文字「あ」は、Unicodeでは「3042」、utf8では「E3 81 82」
- Unicodeは当初の設計上、2バイト一文字で表現しようとしていたが、表現する文字が増えてきたため、2バイト*2で
一文字で表現する方法が追加された
    - これがサロゲートペアで、前半がU+D800~U+DBFF、後半がU+DC00~U+DFFFの範囲と定義されている
- UTF-16ではサロゲートペアで表されるような、基本多言語面外の符号位置をUTF-8で表す時は（中略）U+10000~U+10FFFFの符号位置にデコードしてから変換する（[Wiki](https://ja.wikipedia.org/wiki/UTF-8)参照）
    - BPMの表現 = UTF8では3バイト文字で表現可能
    - 1~3バイト内で表現しきれなかったutf8の文字を表現するために4バイト使って表現する
        - utf8mb3ではこの4バイト文字が入らない

こちらの話から、utf8においては、Unicodeのサロゲートペアはutf8で4バイトで符号化されるのと同義であることがわかりました（でいいのかな？たぶん）。
理解力が乏しいせいか、なかなか把握できず困りました。。。。

でこれをいざJavaでかいてみるとするとどうなるか。
いったん、上記の話を確かめるためJshellで確認してみます。

```
import java.util.stream.IntStream;

String word = "𠮷";
System.out.println("this word has length of " + word.length());

if (word.length() != word.codePointCount(0, word.length())) {
    System.out.println("code point count " + word.codePointCount(0, word.length()));
}

IntStream.range(0, word.length()).forEach(i -> {
    System.out.println("code point is " + Integer.toHexString(word.codePointAt(i)));
    var target = word.charAt(i);
    if (Character.isSurrogate(target)){
        System.out.println("this is surrogate pair");
    }
    System.out.println("check "+ target);
});

-------------------
this word has length of 2

code point count 1

code point is 20bb7
this is surrogate pair
check ?
code point is dfb7
this is surrogate pair
check ?

```

ちなみに𠮷は「つちよし」で変換できます。
ためしにMysql5.7のvarcharにいれるとエラーになりました。
```Incorrect string value: '\xF0\xA0\xAE\xB7' for column 'name' at row 1```
名前にいれられないとか。。。。
まあ、今回の要件はそこではないのでよいのです。

javaのcharには2バイト文字が格納されるので、string.length()は文字数でなく、左記ユニット数がカウントされます。
コードポイントが実際のUnicodeで表現する文字１つにあたるため、サロゲートペアを確認する時はcodePointCountと比較することも可能です。

## 結論？

ということで、結局サロゲートペア = UTF8の4バイト文字の認識でよい、のかな？
どちらにしろ、```Character.isSurrogate()```が最も便利そうなので、これでサロゲートペアを確認し、４バイト文字を制限することとします。

## 参考

- 【図解】【3分解説】UnicodeとUTF-8の違い！【今さら聞けない】
    - https://qiita.com/omiita/items/50814037af2fd8b2b21e
- Unicodeがどんな風にUTF-8に割当てられているか
    - http://orange-factory.com/dnf/unicode.html
- UTF-8の符号化方法について
    - https://www.go-next.co.jp/blog/web/php/25499/
- Unicode  【 ユニコード 】
    - [e-word](http://e-words.jp/w/Unicode.html#:~:text=Unicode%E3%81%A8%E3%81%AF%E3%80%81%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%97%E3%81%9F%E3%82%82%E3%81%AE%E3%80%82)
- 符号化文字集合と文字符号化方式 - 「プログラマのための文字コード技術入門」を読んだ
    - https://blog.shibayu36.org/entry/2015/09/14/102100
- MySQL で utf8 と utf8mb4 の混在で起きること
    - https://tmtms.hatenablog.com/entry/2016/09/06/mysql-utf8