My Note Pad

エンジニアリングや日々の雑感を書いていきます

複数SIMカードを使い分けている話

どうしてこの記事を書こうと思ったのか

近年、スマートフォンがますます重要なデバイスとなってきており、重要な情報がスマートフォンにいると思います。
仮に利用しているスマートフォンが紛失・破損した際に色々と不便を被る可能性が高くなっています。
そのため、私はスマートフォンを複数保持しており、多重化を行なっています。それに合わせて、SIMカードも複数利用しているのでそれについて紹介していきます。

利用端末

以下の端末を利用しています。

メイン端末: iPhone 14 Pro Max

www.apple.com

サブ端末: Pixel 7

store.google.com

iPhoneAndroidどちらも利用しておきたいので、各OSで欲しい端末を揃えている形です。
また、iPhoneは毎年Apple Storeで買い替えるようにしています。
iPhoneは綺麗に使っていればリセールバリューが高いので、毎年数万円で最新のiPhoneが利用できます。

Android端末としてPixel 7を選択したのには以下の理由があります。

  • コスパが非常に良かった。
  • Google純正端末なのでサポート期間が他メーカーに比べて長い
  • キャリアを経由せずに購入できる
  • 技適のあるGalaxy Sシリーズがキャリアからしか購入できない

利用しているSIMカード

そんな中、SIMカードは以下のものを利用しています。

iPhone 14 Pro Max

  • IIJ mio タイプD 4ギガプラン 音声SIM (物理SIM)
  • povo 2.0 (eSIM)

www.iijmio.jp

povo.jp

Pixel 7

network.mobile.rakuten.co.jp

使い分け

基本的にはiPhone 14 Pro Maxのみを持ち歩いており、メインのSIMカードIIJ mioとなっています。
IIJ mioが通信できない時や、旅行などでWi-Fiが利用できない時間が長い時は、Povoでトッピングを追加して利用しています。
そのため、こちらは月額 990円で利用できています。(音声通話やPovoの利用がない場合)

Pixel7はほぼ家の中でしか利用していません。そのため、楽天モバイルは 月額 1,078円で利用できています。
解約やPovoなどの維持費が安いSIMに移行する手もありますが、楽天経済圏の恩恵を受けられているため現状は継続予定です。

合計すると、月の通信費は 2,068円となります。

現状はこれだけのコストで運用できているため、非常に満足度が高いです。

一方、上記内容と重複する部分もありますが、不満点です。

  • IIJ mioはMVNOという特性上、平日昼間や夕方に極端に回線速度が低下することがある。
  • 外で利用していない楽天モバイルの維持費がそこそこ高い。
  • 契約しているSIMが3社に及ぶ

今後

ausoftbankがデュアルSIMを検討するなど、この分野についても新しいサービスがどんどん出てきそうです。

www.itmedia.co.jp

個人的には、リモートワーク主体なので現状であまり不満もありませんが、外出機会が増えてメイン回線の速度や容量で不満を感じるようなことがあれば、メイン回線を ahamoなどに切り替える検討をするかもしれません。
引き続き、情報感度を高くして最適な状態を検討して行きたいと思います。

AirPods Proの修理サービスプログラムに申し込んでみた。

2019年12月に購入した AirPods Proですが、Web会議中に自分が声を発する際、左側のAirPods Proからカサカサという音が聞こえるようになりました。

www.apple.com

最初はイヤーチップが合わなくなってきたのかな?と思い、掃除をしてみたり別のサイズに変えてみたりしましたが、症状は改善せず、むしろ徐々に悪化していく状況となりました。
そんな折、AirPods Proの修理プログラムがあることを知り、事象も近そうだということでAppleのサポートに問い合わせをしてみました。

対象の修理プログラムは↓こちら

support.apple.com

今回はチャットサポートをお願いしました。
接続まで2分と表示されていましたが、すぐに繋がりました。

チャットサポート

※チャットサポート全体で、20~30分くらいの時間を要しました。

チャットでは主に以下のことを質問されました。

  • 事象の確認
  • AirPods Proのシリアル番号
  • 接続しているiOSのバージョン
  • Bluetooth/アクティブノイズキャンセリング ON/OFFで事象が変わらないか
  • 耳に装着した状態でタップし、異音がするか
  • 傷や水没がないか

諸々のヒアリングの後、無償修理プログラムの対象に該当するということになり、更に追加で以下の情報を確認されました。

  • 名前
  • 住所
  • メールアドレス

その後すぐに、Appleより以下の文面のメールが届き、クレジットカード情報を入力します。
※入力したタイミングで、10,780円(左耳分)のクレジットカード与信枠を確保されます。
Appleに故障したAirPods Proが到着し、過失による問題でないと確認された後、取り消しされるそうです。
※返送されない、過失が認められた場合はそのまま請求されるようです。

f:id:yuki10k:20210113230446p:plain

クレジットカード情報を登録したタイミングで完了のメールが届きます。

f:id:yuki10k:20210113230525p:plain

さらにしばらくすると、今後の流れを記載したメールが届きました。

f:id:yuki10k:20210113230608p:plain

チャットサポート完了後

チャット自体は夜だったので、発送の連絡は翌日に届き、さらにその翌日に交換品が届きました。
受け取りの際に、故障したAirPods Proの左側からイヤーチップを渡すのですが、
その場で届いた箱を開封 → 中身の小さい箱と説明書を取り出し → 届いた箱に故障したAirPods Proを入れて渡す
という流れで、少し戸惑いました。(Appleの修理プログラム自体が初のため・・)

受け取ったのは説明書と、シンプルな箱です。

f:id:yuki10k:20210113225140j:plain

箱の中には、左側のAirPods Proの交換品。

f:id:yuki10k:20210113225205j:plain

さらにイヤーチップの別サイズ。

f:id:yuki10k:20210113225224j:plain

一番下には、リセットの手順の図が書かれていました。

f:id:yuki10k:20210113225247j:plain

その後、接続をリセットして新しいAirPodsとして接続し直したところ、無事ノイズも乗らず快適に使えるようになりました。

まとめ

Appleの修理プログラムの利用自体は初めてで、チャットでの確認は少し戸惑う部分もありましたが、交換品のパーツ発送もすぐに行われ、全体のプロセスが洗練されていると感じました。
故障したAirPods Proの到着確認がまだ完了しておらず、請求のキャンセルはまだされていない状況ですが、リファービッシュ品との交換と修理プログラムのスムーズさを考えると妥当かと感じました。

まだAirPods Proは購入してから1年と少ししか使っていないので、今後もバリバリ使っていこうと思います。

macでUS配列とJIS配列の両方を切り替えて使う場合の設定

年末にM1チップを搭載した MacBook Airを購入しました。
購入したモデルは一番下のモデル(8GB RAM / 256GB SSD)です。

www.apple.com

MacBook Airを購入した理由

本来は来年発売されると噂されている14インチ MacBook Proまで待ちたい所でしたが、

  • メインPCの調子が悪い。
  • US配列やRAM16GBへのカスタマイズは1月下旬から2月頭までかかる。
  • Amazonでたまたま在庫が復活したのを発見した。(通常は1~2ヶ月待ちの表示)
  • MacBook Airは繋ぎとして利用し、高スペックのMacBook Proが発売されたら US配列にカスタマイズして購入する予定

といった理由で購入しました。

しかし、普段(仕事用のMacBook Proや以前使っていたPC)はUS配列のキーボードを使っているため、JIS配列のキーボードで問題ないかという懸念がありました。 実際に使って、使いにくい部分もありながらも「これならいけそう」という状態に持っていけたので、記事として書いてみました。

キーボードの種類

※ Keychronと書いていますが、通常のUS配列キーボードなら何でも適用できるはずです。

必要なソフトウェア

定番の Karabiner-Elements を使用します。

karabiner-elements.pqrs.org

設定方法

Karabiner-Elementsの preferences... から、Complex modifications で設定していきます。

Add Rulesから以下のページを開きます。

karabiner-elements-complex_modifications

Japanese などで検索し、

f:id:yuki10k:20210103222551p:plain

以下の2つを import します。

  • For Japanese (JIS配列をASCII配列風にする設定)
  • For Japanese (日本語環境向けの設定) (rev 5)
本体キーボード向けの設定

以下のルールを有効にします。

f:id:yuki10k:20210103215045p:plain
JIS

この設定では、以下の挙動になります。
「英数」: 英数に切り替え / 別のキーと同時押しでCommandとして扱う
「かな」: 日本語入力に切り替え / 別のキーと同時押しでCommandとして扱う
Command: Option

また、Google日本語入力を利用している場合、¥マークの入力で \ (バックスラッシュ) に切り替えることができます。

f:id:yuki10k:20210103223544p:plain
Google日本語入力

US配列向けの設定

US配列向けのProfileを設定します。
ここでは、Keychronという名前で設定しました。

f:id:yuki10k:20210103223050p:plain
profileの追加

ProfileをKeychronにした状態で、以下の設定をしていきます。

f:id:yuki10k:20210103215149p:plain
US

この設定では、以下の挙動になります。
左Command: 英数に切り替え / 別のキーと同時押しでCommandとして扱う
右Command: 日本語入力に切り替え / 別のキーと同時押しでCommandとして扱う

利用するキーボードを切り替える場合

面倒ですが、メニューバーのKarabiner-Elementsから、使いたいキーボードに合わせてプロファイルを切り替えます。

f:id:yuki10k:20210103215315p:plain
プロファイルの切り替え

また、たまにMacが認識しているキーボードが切り替わらない場合があるので、その際はシステム環境設定 -> キーボード -> キーボードの種類を変更 で再認識させることができます。

f:id:yuki10k:20210103223720p:plain
キーボードの切り替え

まとめ

MacBook Air自体は、他の方のレビューにもある通りサクサクで、通常のユースケースであれば吊るしモデルでも快適に使えるのではと思いました。
やはり普段US配列のキーボードを使っているとJIS配列にはなかなか慣れず、普段はクラムシェルっぽく利用して外付けUSキーボードで運用し、ソファーなどで利用する際に本体のキーボードを使うという運用に落ち着きそうです。
バッテリー持ちも良く、本体キーボードも以前のモデルより打ちやすくなっているため、高性能MacBook Proの発売が楽しみになりました。

新しいKyashCardが到着!

KyashCardとは?

Kyash社が提供している Kyashというサービスの物理カードです。
今回、新たにタッチレス決済に対応しています。

kyash.co

KyashCardは何が嬉しいのか

以下のメリットがあるので、昔からKyashを利用しています。

ポイントの二重取りができる

KyashCardは決済金額の1%Kyashのポイントとして還元されます。
このポイントは1ポイント = 1円としてKyashの残高にチャージすることができます。
さらに、Kyashに登録しているクレジットカードで決済されるため、クレジットカードのポイントも貯まるのでポイントが二重取りできます。
さらに、PayPayのクレジットカードをKyashとして登録すると PayPay0.5%も上乗せされます。
PayPayしか使えないというお店も多いので、PayPayも登録すると便利です。
1点デメリットとして、KyashCardは3Dセキュアに対応しないため、PayPayでは直近30日で5,000円の制限がかかってしまいます。

また、モバイルSuicaの支払い元カードをKyashに設定すると、Kyashのポイントは貯まりませんがその先のクレジットカードのポイントは通常通り貯まります
これはクレジットカードの請求元がKyashになるからだと思われます。

KyashCardのデメリット

あまりないのですが、以下はデメリットだと感じています。

  • AMERICAN EXPRESSのカードを紐付けできない。
  • 3Dセキュアに対応していない。
  • 発行するのに900円かかる。
  • ポイント還元対象は月12万円の決済まで。

申し込みから手元に届くまで

紆余曲折ありましたが、なんとか無事に手元に到着しました。

時系列

2/25 KyashCardの申し込み開始
2/25 お昼前くらいに申し込み完了
2/26 Kyashの決済が失敗し、ロックされていることが判明し問い合わせ
2/27 問い合わせの回答としてロック解除の連絡があり、解除される
2/27 KyashCardの申し込みのステータスがリセットされており、再度問い合わせ
3/3 テンプレ回答なのでスルーしていたら、申込みステータスが復活
.
.
.
4/11 手元に到着

到着

申し込んだと同じ色のシンプルな封筒で到着しました。

f:id:yuki10k:20200411132755j:plain

中身もシンプルです。 f:id:yuki10k:20200411132750j:plain

カードの有効化はアプリから行うことができます。
※上記のQRコードが読み込めなかったので手動で切り替えました。

この画面から数ステップであっという間に登録完了でした。 f:id:yuki10k:20200411132815j:plain

最後に、旧カードとの比較です。
表面からカード番号や KYASH MEMBERという名前がなくなって非常にシンプルなデザインになりました。 f:id:yuki10k:20200411132854j:plain

AWS SAMでローカル環境でS3とDynamoDBを扱うLambdaを実行する

この記事は2018年10月26日にQiitaに投稿した記事を移行したものです

この投稿について

Serverlessconf Tokyo 2018で色々と刺激を受け、Lambdaに取り組んでみようと思い、色々と試す上でLambdaをローカル環境で開発や動作確認をするのに色々迷う部分が多かったので、メモとして残したものです。

動作環境

以下のものを使用しています。

# Pythonはvenv
$ python --version
Python 3.6.6
$ aws --version
aws-cli/1.16.24 Python/3.7.0 Darwin/18.0.0 botocore/1.12.14
$ sam --version
SAM CLI, version 0.6.0

SAMプロジェクトの作成

sam initコマンドを使用して空のプロジェクトを作成します。

  • -r はruntime
  • -n はプロジェクト名

を指定しています。

$ sam init -r python3.6 -n sam-s3-lambda-app

無事に成功すると以下のようなパッケージ構成が作成されます。

$ cd sam-s3-lambda-app
$ tree .
.
├── README.md
├── hello_world
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-37.pyc
│   │   └── app.cpython-37.pyc
│   └── app.py
├── requirements.txt
├── template.yaml
├── tests
│   └── unit
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-37.pyc
│       │   └── test_handler.cpython-37.pyc
│       └── test_handler.py

hello_worldの動作確認

まずは自動的に作成された、hello_world関数を実行してみます。

# ライブラリのインストール
$ pip install -r requirements.txt -t hello_world/build/
$ cp hello_world/*.py hello_world/build/
# イベントの生成
$ sam local generate-event apigateway aws-proxy > api_event.json
# テスト実行
$ sam local invoke HelloWorldFunction --event api_event.json
2018-10-25 18:31:13 Invoking app.lambda_handler (python3.6)

Fetching lambci/lambda:python3.6 Docker container image......
.
.
.

{"statusCode": 200, "body": "{\"message\": \"hello world\", \"location\": \"IP Address\"}"}

無事に動作確認ができました。

実際にS3とDynamoDBを扱う部分を実装

LocalStackコンポーネントの作成

今回はS3とDynamoDBをエミュレートするために、LocalStackを使用しました。 https://github.com/localstack/localstack

LocalStack自体はdockerで立ち上げます。

以下のようにdocker-compose.ymlファイルを作成します。

version: "3.3"

services:
  localstack:
    container_name: localstack
    image: localstack/localstack
    ports:
      - "4569:4569"
      - "4572:4572"
    environment:
      - SERVICES=dynamodb,s3
      - DEFAULT_REGION=ap-northeast-1
      - DOCKER_HOST=unix:///var/run/docker.sock

s3とdynamodbがそれぞれ使用するポートはこちら

  • s3:4572
  • dynamodb:4569

LocalStackの起動 ※初回はイメージのダウンロードがあるため、時間がかかります。

$ docker-compose up

LocalStack用credentialの作成

LocalStack用にcredential情報を追加します。

[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy
[profile localstack]
region = ap-northeast-1
output = json

LocalStack上にDynamoDBのテーブルを作成

AWSのドキュメントを参考にテーブルを作成します。 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Tools.CLI.html

$ aws dynamodb create-table \
>     --table-name Music \
>     --attribute-definitions \
>         AttributeName=Artist,AttributeType=S \
>         AttributeName=SongTitle,AttributeType=S \
>     --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \
>     --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
>     --endpoint-url http://localhost:4569 --profile localstack

$ aws dynamodb list-tables --endpoint-url http://localhost:4569 --profile localstack
TABLENAMES      Music

LocalStack上のS3にテストデータを配置

LocakStackのS3にバケットの作成と、実ファイルの配置を行います。

# bucketの作成
$ aws s3 mb s3://music/ --endpoint-url=http://localhost:4572 --profile localstack
make_bucket: music
# テストデータのput
$ aws s3 cp ./testdata.json s3://music/rawdata/testdata.json --endpoint-url=http://localhost:4572 --profile localstack
upload: ./testdata.json to s3://music/rawdata/testdata.json  

テストデータは以下の内容で作成しました。

{
        "Artist": "Acme Band",
        "SongTitle": "Happy Day",
        "AlbumTitle": "Songs About Life"
}

関数のベースを作成

まずは依存関係にboto3を追加します。 boto3PythonでS3やDynamoDBなどのリソースを扱うためのライブラリです。 https://github.com/boto/boto3

$ pip install boto3
$ pip freeze > requirements.txt

関数の作成 今回はs3_dynamoという関数にしました。

$ mkdir s3_dynamo
$ touch s3_dynamo/__init__.py s3_dynamo/app.py

SAMのテンプレートのResourcesセクションに、作成した関数を追記します。

Resources:
    S3DynamoFunction:
      Type: AWS::Serverless::Function
      Properties:
          CodeUri: s3_dynamo/build/
          Handler: app.lambda_handler
          Runtime: python3.6

ローカル実行用のプロファイルとして、以下のファイルも合わせて作成しました。

{
  "S3DynamoFunction": {
    "AWS_SAM_LOCAL": true
  }
}

S3イベントの作成

SAM CLIの機能で各Lambda関数のイベントを生成できるようになっているので、S3をputするイベント定義を作成します。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/test-sam-cli.html

わかりにくいですが、 --keyにはS3のバケット配下のパスを渡します。

$ sam local generate-event s3 put --bucket music --key rawdata/testdata.json --region ap-northeast-1 > s3_event.json 

出力されたイベントファイルがこちら。 awsRegion,bucket.name,object.key が合っていれば正しくLambda側で判定できそうです。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
.
.省略
.
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "music",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::music"
        },
        "object": {
          "key": "rawdata/testdata.json",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

S3からの読み込み処理を実装

まずはS3からファイルを読み取れることを確認するために、以下のようにS3からファイルを読んで標準出力に表示する部分だけを実装します。

import os
import json
import boto3
import pprint
import urllib.parse

if os.getenv("AWS_SAM_LOCAL"):
    dynamodb = boto3.resource(
        'dynamodb',
        endpoint_url='http://host.docker.internal:4569/'
    )
    s3 = boto3.client(
        's3',
        endpoint_url='http://host.docker.internal:4572/'
    )
else:
    dynamodb = boto3.resource('dynamodb')
    s3 = boto3.client('s3')


def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    print("[bucket]: " + bucket + " [key]: " + key)

    try:
        response = s3.get_object(Bucket=bucket, Key=key)
        d = json.loads(response['Body'].read())
        pprint.pprint(d)
    except Exception as e:
        print(e)
        raise e

env-local.jsonを使用して実行するため、今回はS3 / DynamoDBとしては以下の定義が使われます。 sam localで実行する場合もdocker内部から実行されるようで、ホスト名はhost.docker.internalを使っています。

    dynamodb = boto3.resource(
        'dynamodb',
        endpoint_url='http://host.docker.internal:4569/'
    )
    s3 = boto3.client(
        's3',
        endpoint_url='http://host.docker.internal:4572/'
    )

実際に実行してみます。

$ pip install -r requirements.txt -t s3_dynamo/build
$ cp s3_dynamo/*.py s3_dynamo/build/
$ sam local invoke S3DynamoFunction --event s3_event.json --profile localstack

# result
[bucket]: music [key]: rawdata/testdata.json
{'AlbumTitle': 'Songs About Life',
 'Artist': 'Acme Band',
 'SongTitle': 'Happy Day'}

無事にbuket、keyが渡され、S3ファイルの中身を表示することができています。

DynamoDBへputするコードの実装

以下が最終型です。

import os
import json
import boto3
import pprint
import urllib.parse

if os.getenv("AWS_SAM_LOCAL"):
    dynamodb = boto3.resource(
        'dynamodb',
        endpoint_url='http://host.docker.internal:4569/'
    )
    s3 = boto3.client(
        's3',
        endpoint_url='http://host.docker.internal:4572/'
    )
else:
    dynamodb = boto3.resource('dynamodb')
    s3 = boto3.client('s3')

table = dynamodb.Table('Music')


def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    print("[bucket]: " + bucket + " [key]: " + key)

    try:
        response = s3.get_object(Bucket=bucket, Key=key)
        d = json.loads(response['Body'].read())
        pprint.pprint(d)

        table.put_item(
            Item=d
        )
    except Exception as e:
        print(e)
        raise e

わかりにくいですが、以下のコードを追加しています。

table = dynamodb.Table('Music')
.
.
.
        table.put_item(
            Item=d
        )

LambdaのTimeoutを伸ばす

デフォルトではLambdaの実行時間上限は3秒になっているので、適当に伸ばしておきます。

Globals:
    Function:
        Timeout: 100

結果確認

再度実行してみます。

$ cp s3_dynamo/*.py s3_dynamo/build/
$ sam local invoke S3DynamoFunction --event s3_event.json --profile localstack

AWS CLIでMusicテーブルの中身を確認

$ aws dynamodb scan --table-name Music --endpoint-url http://localhost:4569 --profile localstack
1       1
ALBUMTITLE      Songs About Life
ARTIST  Acme Band
SONGTITLE       Happy Day

無事にDynamoDBのテーブルにデータがputされています。

おわりに

今までLambdaのコンソール上で短いコードを書いて実行するといったことはやったことがありましたが、実際にローカルに開発環境を用意して実行してみるという部分は初めてだったので、ハマりどころや分からない部分も多くありました。 実際にSAMを使用してAWS上にデプロイするにはCloudFormationを使うなど、もう少し学ぶところがありそうだという印象でした。 Serverlessのメリットを享受するために、これから吸収していければと思います。

Spring Boot 1.4系から2.0系へのマイグレーションでやったこと

これは2018年08月07日にQiitaに投稿した記事を移行したものです

はじめに

前回は、SpringBoot 1.5からSpring Boot 2.0へのバージョンアップを行いました。 今回は別のプロジェクトで、1.4から2.0に上げた際にハマった事などを書いていきます。

yuki10.hatenablog.com

今回主に苦労したのは、

  • 問題の原因が 1.4->1.5の部分なのか、1.5->2.0の部分なのかの切り分けに時間がかかった
  • Thymeleafの影響が大きいのでテストが大変
  • SpringSessionでのシリアライズ/デシリアライズまわり
  • 本番デプロイ時の対策

あたりです。

Spring Bootのバージョンを上げる

spring-boot-starter-parentのバージョンを更新

pom.xmlでSpringBootのバージョンを指定します。

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath />
  </parent>

HikariPCの依存関係を削除

元々HikariPCを使用していたので、依存関係から削除します。

    <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    </dependency>

コンパイルしてエラーを潰していく

SpringBootServletInitializerが見つからない

1.png

SpringBootServletInitializer のパッケージが変わっているので、再importする

org.springframework.boot.context.embedded.が見つからない

2.png

パッケージが org.springframework.boot.web.servlet. に変わっているので再importする

DataSourceBuilderが見つからない

3.png

パッケージが変わっているので再importする

WebMvcConfigurerAdapterの非推奨化

extends WebMvcConfigurerAdapterimplements WebMvcConfigurer に変更する

@EnableWebMvcSecurityの非推奨化

@EnableWebSecurity に変更する。

https://docs.spring.io/spring-security/site/docs/current/reference/html/mvc.html

org.apache.velocity.appが見つからない

4.png

velocity から mustache に移行する。

-    <dependency>
-   <groupId>org.apache.velocity</groupId>
-   <artifactId>velocity</artifactId>
-    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-mustache</artifactId>
+    </dependency>

velocityのテンプレートファイル *.vm を mustacheのテンプレートファイル *.mustache に置換する。 ※ここは application.properties などで変更可

mustacheのテンプレートの形式に合わせて

${hoge}
↓
{{hoge}}

こんな感じで全部置き換える

shellとかで一括置換してもいいけど、IntelliJのリファクタでやると呼び出し元コードも発見して教えてくれるので数が少なければIntelliJのリファクタがいいかもしれない。

org.jsonが見つからない

5.png

以下の依存関係を追加する

  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
    </dependency>

以下のパッケージで importしなおす org.springframework.boot.configurationprocessor.json.JSONObject

SpringApplication.runでエラー

6.png

SpringApplication.runの引数が変更になっている

以下のように修正

        public static void main(String[] args) {
-               Object[] objects = { HogeApplication.class, FugaService.class };
-               SpringApplication.run(objects, args);
+               final SpringApplication application = new SpringApplication(HogeApplication.class, FugaService.class);
+               application.run(args);
        }

ついでに以下のクラスを継承する

SpringBootServletInitializer

JPAのメソッド変更まわりの対応

7.png

黙々と直していく

https://spring.io/blog/2017/06/20/a-preview-on-spring-data-kay#improved-naming-for-crud-repository-methods

AutoConfigureTestDatabaseが見つからない

8.png

import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; から import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; にパッケージを変更

Thymeleafのマイグレーション

https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html

th:substitutebyを th:replaceに置き換える

こんな感じで機械的

find src/main/resources -type f -name "*.html" -print | xargs sed -i  -e 's/th:substituteby/th:replace/g'

linkタグのcss読み込みのtype="text/css"を削除

こんな感じで機械的

find src/main/resources -type f -name "*.html" -print | xargs sed -i  -e 's@type=\"text/css\"@@g'

inline="text" / inline="inline"を削除

一応中身を見ながら、削除していく。

Scriptタグ内のthymeleafが展開されない

こういうやつ

<script>(window.dataLayer || (window.dataLayer = [])).push(<span th:remove="tag" th:utext="${hoge}"/>)</script>

こんな風に修正する

<script type="text/javascript" th:inline="javascript">/*<![CDATA[*/
     (window.dataLayer || (window.dataLayer = [])).push(/*[(${hoge})]*/)
 /*]]>*/</script>

その他

SpringBootでは@EnableWebMvcがついていたら削除する

SessionScope

SpringSecurityの onAuthenticationSuccess 実行時に @SessionScope を参照できなくてエラー Error creating bean with name 'user': Scope 'session' is not active for the current thread;

以下のようなコンフィグファイルを作成しておいておく

package jp.hoge;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextListener;

import javax.servlet.annotation.WebListener;

@Configuration
@WebListener
public class WebRequestContextListener extends RequestContextListener {
}

Hibernate SaveAndFlushメソッドでエラー

java.sql.SQLSyntaxErrorException: Table 'hoge.hibernate_sequence' doesn't exist
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:536)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115)
    at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
    at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1826)
    at com.mysql.cj.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1923)
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)

GenerationType を変更する

-    @GeneratedValue(strategy=GenerationType.AUTO)
+    @GeneratedValue(strategy=GenerationType.IDENTITY)

実行中にHibernateエラー

org.springframework.dao.InvalidDataAccessResourceUsageException: error performing isolated work; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: error performing
 isolated work
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:242)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
        at com.sun.proxy.$Proxy127.save(Unknown Source)
        at jp.hoge.service.FugaService.execute(FugaService.java:218)
        at jp.hoge.controller.FugaController.execute(FugaController.java:101)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)

HibernateのGenerator mappingsが変更になっている。

application.propertiesに以下の設定を追加することで対処

spring.jpa.hibernate.use-new-id-generator-mappings=false

STGなどへのデプロイ後

プロファイルの読み込みエラー

logback-spring.xml<springProfile> でプロファイルが正しく読み込めずにエラー executableJarの場合、-Dspring-boot.run.profiles=環境名 のように定義すればいい。

今回のプロジェクトではtomcatにwarをデプロイするので、 application.propertiesspring-boot.run.profiles=環境名 みたいに定義してやる。

実際のところは、pom.xmlでwarファイル生成時にプロファイルを分けているので、以下のように定義しています。

spring-boot.run.profiles=${spring.profiles.active}
<profiles>
        <profile>
            <id>local</id>
            <properties>
                <spring.profiles.active>local</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>stg</id>
            <properties>
                <spring.profiles.active>stg</spring.profiles.active>
            </properties>
        </profile>
</profiles>

ビルド時にプロファイル

mvn package -Pstg

という感じで、 application.properties に埋め込んでいる。

SpringSecurity経由でのログイン後にSession取得後のUserオブジェクトのメンバ変数が全てnullになっている

Userのメンバ変数にアクセスしようとしてNullPointerExepotionが発生 とはいえ、Redisの中身を見てもシリアライズされているため読めない・・・

※Memberは @SessionScope

一旦、以下のような感じでJSONに変換してRedisに登録してくれるSerializerを作成する

@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis", matchIfMissing = false)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 20 * 24 * 60 * 60) //default=1800 -> 20days
@Configuration
public class HttpSessionConfig implements BeanClassLoaderAware {

   @Autowired
   private LettuceConnectionFactory lettuceConnectionFactory;

   private ClassLoader classLoader;

   @Bean
   public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
    final ObjectMapper mapper = new ObjectMapper()
            .registerModules(SecurityJackson2Modules.getModules(classLoader))
            .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    return new GenericJackson2JsonRedisSerializer(mapper);
  }

      // Elasticache用
      @Bean
      public static ConfigureRedisAction configureRedisAction() {
          return ConfigureRedisAction.NO_OP;
      }

    @Override
    public void setBeanClassLoader(final ClassLoader classLoader) {
        this.classLoader = classLoader;
    }
}

そうするとlogin完了後からControllerに遷移する際に、全フィールドnullの状態でRedisにセットされていた。

ログイン後のControllerで User オブジェクト内の idがnullかどうか判定し、nullであれば詰め直すように対応。 ログイン以降は問題なくセッションを扱えている。

本当はJSON形式でセッション情報を持てるようにしたかったものの、Userクラスの構造が複雑なネスト構造になっていたため、時間との兼ね合いで断念・・・ Userクラスに情報をもたせすぎ・・・・

本番デプロイ時の対応

SpringSession のバージョンアップに伴い、内部的にシリアライズ・デシリアライズで扱う SerialVersionUID が変わっているらしいので、バージョンアップしたコードをデプロイすると、既にログイン中のユーザーはセッション情報をデシリアライズできなくて詰む。

なので、デシリアライズするときの対応を入れる。

https://sdqali.in/blog/2016/11/02/handling-deserialization-errors-in-spring-redis-sessions/

少し記事が古く、依存ライブラリが変更になっているので、以下の通り変更している。

- public class HttpSessionConfig {
+ public class HttpSessionConfig extends RedisHttpSessionConfiguration {

+   @Autowired
+   RedisTemplate<Object, Object> redisTemplate;
+   @Bean
+   @Override
+   public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
+       return super.springSessionRepositoryFilter(new SafeDeserializationRepository<>(sessionRepository, redisTemplate));
+   }
public class SafeDeserializationRepository<S extends Session> implements SessionRepository<S> {
    private final SessionRepository<S> delegate;
    private final RedisTemplate<Object, Object> redisTemplate;
     private static final String BOUNDED_HASH_KEY_PREFIX = "spring:session:sessions:";
     public SafeDeserializationRepository(SessionRepository<S> delegate,
            RedisTemplate<Object, Object> redisTemplate) {
        this.delegate = delegate;
        this.redisTemplate = redisTemplate;
    }
     @Override
    public S createSession() {
        return delegate.createSession();
    }
     @Override
    public void save(S session) {
        delegate.save(session);
    }
     @Override
    public S findById(String id) {
        try {
            return delegate.findById(id);
        } catch(SerializationException e) {
            log.info("Deleting non-deserializable session with key {}", id);
            redisTemplate.delete(BOUNDED_HASH_KEY_PREFIX + id);
            return null;
        }
    }
     @Override
    public void deleteById(String id) {
        delegate.deleteById(id);
    }
}

これで既存のユーザーは、一度ログアウトさせられ、再ログインで新しいSessionデータをRedisに保存し、以降はそちらを参照する。

まとめ

大体、上記の対応で本番環境で動作するようになりました。 (他にもある気がするけど)

また、本来であればTomcatは8.5系以上にしなければならないのですが、8.0で問題なさそうであったため、Tomcatのバージョンアップは見送りました。

SpringBootのメジャーバージョンをいくつか飛ばしてしまうと、とてもつらいのでバージョンアップはこまめにやろう・・・!!

参考ページ

Spring Boot 1.5系から2.0系へのマイグレーションでやったこと

これは2018年07月01日にQiitaに投稿した記事を移行したものです

はじめに

こちらでKotlin対応を行ったプロジェクトに対して、SpringBoot 2.0.2にアップデートを行いました。 ここではそのときに発生した作業などについて書いていきます。

※Spring Boot2.0からはJava8以降が必須になっています。

また、このプロジェクトはREST APIのサーバーとして動作しているため、Thymeleafのマイグレーションなどは回避することができました。 別途、Spring MVCのプロジェクトをマイグレーションしているので、そのあたりは別で書こうと思います。

以下のサイトや記事を主に参考にさせていただきました。

まずはSpring Bootのバージョンアップ

pom.xmlを修正してSpring Bootのバージョンを上げていきます。

変更点を抜粋

    <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
 -    <version>1.5.8.RELEASE</version>
 +    <version>2.0.2.RELEASE</version>
      <relativePath />
    </parent>

・・・

      <dependency>
        <!-- https://docs.spring.io/spring-session/docs/current/reference/html5/guides/java-redis.html -->
        <groupId>org.springframework.session</groupId>
 -      <artifactId>spring-session</artifactId>
 +      <artifactId>spring-session-data-redis</artifactId>
      </dependency>

・・・

      <!-- https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#jackson--json-support -->
 +    <dependency>
 +      <groupId>org.springframework.boot</groupId>
 +      <artifactId>spring-boot-starter-json</artifactId>
 +    </dependency>

・・・
      <!- 元々HikariPCを使用していたので依存関係を削除 -->
      <!-- https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#configuring-a-datasource -->
 -    <dependency>
 -      <groupId>com.zaxxer</groupId>
 -      <artifactId>HikariCP</artifactId>
 -    </dependency>

・・・

      <!-- ここはテストが終わったら削除 ->
      <!-- https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#before-you-start -->
 +    <dependency>
 +      <groupId>org.springframework.boot</groupId>
 +      <artifactId>spring-boot-properties-migrator</artifactId>
 +      <scope>runtime</scope>
 +    </dependency>

ApplicationクラスのWebMvcConfigurerAdapterを使わないようにする

- public class XxxApplication extends WebMvcConfigurerAdapter {
+ public class XxxApplication implements WebMvcConfigurer{

ビルド->修正->再ビルド

pom.xmlの設定が終わったら、ビルドして修正を繰り返していきます。

JPA RepositoryのfindOneでコンパイルエラー

まずは以下のようなエラーが発生しました。

Error:(41, 41) Kotlin: Type inference failed: fun <S : #{テーブル名}!> findOne(p0: Example<S!>!): Optional<S!>!
cannot be applied to
(Long)
Error:(41, 49) Kotlin: Type mismatch: inferred type is Long but Example<(???..???)>! was expected

これはSpring DataのCRUDメソッドが変更になっていることに起因します。 https://spring.io/blog/2017/06/20/a-preview-on-spring-data-kay#improved-naming-for-crud-repository-methods

1行目のエラーは、findOneからfindByIdに変更する。 2行目のエラーは、findByIdの戻り値がJavaのOptionalになっているので、Optionalからgetする。

同様に、save, deleteの引数もListを渡すものからEntityを渡すものに変わっているため、代わりに saveAll, deleteAll メソッドを呼ぶようにする。 もちろん、1レコードのみが影響を受けるのであれば、savedeleteを使う方がいいので、マイグレーションに割ける時間と相談で。

ConfigureRedisActionを解決できない

Error:(13, 53) java: パッケージorg.springframework.session.data.redis.configは存在しません

順番が前後していますが、ElastiCacheを利用するために設定している以下の定義で、ConfigureRedisActionが解決できなくなっていました。 これはspring-session-data-redisというパッケージに切り出されたためです。

https://docs.spring.io/spring-session/docs/current/reference/html5/guides/java-redis.html

   @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }

修正内容はpom.xmlspring-sessionの部分を参照。 また、lettuceの依存関係を追加していないのは、元々spring-boot-starter-data-redisが依存関係に含まれているためです。

org.hibernate.validator.constraints.NotBlankなどの非推奨化

これに合わせて、javax.validation.constraints.NotEmptyなどを使うように置き換えています。

jdbcのautoconfigureでエラー

Error:(7, 51) java: シンボルを見つけられません
  シンボル:   クラス DataSourceBuilder
  場所: パッケージ org.springframework.boot.autoconfigure.jdbc

これは複数のデータソースを扱っているために発生しているようでした。

hoge.datasource.driver-class-name        = com.mysql.jdbc.Driver
hoge.datasource.url                    = jdbc:mysql://127.0.0.1:33306/hoge
hoge.datasource.username                = hoge
hoge.datasource.password                = fuga
# その他は省略
# 上記x複数データソース分

元々は以下のように設定されていました

 @Configuration
 @EnableTransactionManagement
 @EnableJpaRepositories(entityManagerFactoryRef = "hogeEntityManagerFactory", transactionManagerRef = "hogeTransactionManager", basePackages = {
        "jp.xxx.data.hoge.repository" })
 public class HogeDatabaseConfig extends AbstractDatabaseConfig {
 
    @Bean(name = "hogeDataSource")
    @ConfigurationProperties(prefix = "hoge.datasource")
    public DataSource hogeDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "hogeEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean hogeEntityManagerFactory(EntityManagerFactoryBuilder builder,
        return builder.dataSource(hogeDataSource)
                .packages("jp.xxx.data.hoge.entity").persistenceUnit("hoge")
                .properties(buildProperties()).build();
    }
 
    @Bean(name = "hogeTransactionManager")
    public PlatformTransactionManager hogeTransactionManager(
            @Qualifier("hogeEntityManagerFactory") EntityManagerFactory hogeEntityManagerFactory) {
        return new JpaTransactionManager(hogeEntityManagerFactory);
    }
 }

が、以下のように修正しました。 この部分についてはもう少しうまいやり方があるんじゃないかなーとは思っています。

 @Configuration
 @ConfigurationProperties(prefix = "hoge.datasource")
 @EnableTransactionManagement
 @EnableJpaRepositories(entityManagerFactoryRef = "hogeEntityManagerFactory", transactionManagerRef = "hogeTransactionManager", basePackages = {
        "jp.xxx.data.hoge.repository" })
 public class HogeDatabaseConfig extends AbstractDatabaseConfig {
 
    @Autowired
    private Environment env;

    @Bean(name = "hogeDataSource")
    public DataSource hogeDataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("hoge.datasource.driver-class-name"));
        dataSource.setUrl(env.getProperty("hoge.datasource.url"));
        dataSource.setUsername(env.getProperty("hoge.datasource.username"));
        dataSource.setPassword(env.getProperty("hoge.datasource.password"));
        return dataSource;
    }
 
    @Bean(name = "hogeEntityManagerFactory")
    public EntityManagerFactory hogeEntityManagerFactory(
            @Qualifier("hogeDataSource") DataSource hogeDataSource) {
        final LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(hogeDataSource);
        factoryBean.setPersistenceUnitName("hoge");
        factoryBean.setPackagesToScan("jp.xxx.data.hoge.entity");
        factoryBean.setJpaPropertyMap(buildProperties());
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factoryBean.afterPropertiesSet();
        return factoryBean.getNativeEntityManagerFactory();
    }
 
    @Bean(name = "hogeTransactionManager")
    public PlatformTransactionManager hogeTransactionManager(
            @Qualifier("hogeEntityManagerFactory") EntityManagerFactory hogeEntityManagerFactory) {
        return new JpaTransactionManager(hogeEntityManagerFactory);
    }
 }

ここでTomcatは起動

Tomcatは起動したものの、以下のエラーログが流れていました。

java.lang.ClassCastException: jp.xxx.service.HogeService$Fuga cannot be cast to org.springframework.core.io.support.ResourceRegion
    at org.springframework.http.converter.ResourceRegionHttpMessageConverter.writeResourceRegionCollection(ResourceRegionHttpMessageConverter.java:182)
    at org.springframework.http.converter.ResourceRegionHttpMessageConverter.writeInternal(ResourceRegionHttpMessageConverter.java:139)
    at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:102)
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:272)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)

データソースにはPostgreSQLもあるので発生しているようです。

これはhibernate.propertiesを作成して配置しておくことで回避しました。

hibernate.jdbc.lob.non_contextual_creation = true

ここまでで起動までにエラーは発生しなくなりました。

突然のClassCastException

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method jp.xxx.controller.HogeController.createFuga, parameter form
    at jp.xxx.controller.HogeController.createCampaign(HogeController.kt)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

formの引数の型をHogeFormからHogeForm?のnull許容型に変換して対応しました。

日付型の変換エラー

Failed to convert value of type 'java.lang.String[]' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2018-06-11T00:00:00'; nested exception is java.lang.IllegalArgumentException

ここは少しハマって原因がよくわからない部分ではあったのですが、データを受け取っていたオブジェクトがKotlinのDataクラスだったものを、普通のKotlinクラスにすることで動作しました。

PUTだとformの値が渡らない

原因を調べる時間がなかったのでPUTしている部分をPOSTに書き換えました・・・

REST Controllerの戻り値の型がList<Object>だとJSONに変換できずにエラーになる。

エラーログ取り忘れ。

元々はこんな実装 そもそもListで返すなと・・・

    @GetMapping("/hoge")
    public List<Object> hoge() {
        return hogeService.hoges();
    }

戻りの型が結構複雑だったので、Controller側でJSONにして返すようにしました。

    @GetMapping("/hoge")
    public String hoge() throws JsonProcessingException {
        final List<Object> hoges = hogeService.hoges();
        final ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(hoges);
    }

以上で、マイグレーションが完了し、移行させることができました。