Spring Data 2.2.8はDomainClassConverterが死んでいるので注意

# 更新情報

2020/07/24に2.2.9がリリースされ無事に以下で説明する不具合は起こらなくなりました。

# はじめに

Spring Dataにはリクエストパラメータ（クエリストリングもしくはフォーム）や`@PathVariable`としてidを渡すとドメインオブジェクトを検索して設定してくれる`DomainClassConverter`という超便利な機能があります。
https://docs.spring.io/spring-data/jpa/docs/2.2.8.RELEASE/reference/html/#core.web.basic

```java:@PathVariableを変換してくれる例
    @GetMapping("/users/{id}")
    public User show(@PathVariable("id") User user) {
        return user;
    }
```

**が、バージョン2.2.8ではこの機能が死んでいます。**
上で例に示した`/users/1`にアクセスすると以下のように500エラーになります。

```
There was an unexpected error (type=Internal Server Error, status=500).
Failed to convert value of type 'java.lang.String' to required type 'com.example.demo.User'; 
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.demo.User': no matching editors or conversion strategy found
```

なお私が報告を受けたのはSpring Data JPAを使っているプロジェクトでフォーム送信（POST）が死ぬという状況だったのですが、この場合、フォームに埋めてある関連オブジェクトを検索しようとして死亡、ステータスとしては400が返されてました。

# 解決方法

ありません。
[不具合報告](https://jira.spring.io/browse/DATACMNS-1743)されており、[修正](https://github.com/spring-projects/spring-data-commons/commit/9c50f86061aafd3c6820728a314fc88ed870361d)もされています。2.2.9ではこの不具合は発生しません。

2.2.9がリリースされるまではpom.xmlをいじるなどして2.2.7に戻しましょう（個人的に致命的なので早めにリリースされてほしい）

# 何が問題なのか

2.2.8に含まれるDomainClassConverterには以下の修正が行われています。
[Defer initialization of Repositories in DomainClassConverter.](https://github.com/spring-projects/spring-data-commons/commit/7af075f45093ca636ccdca76fc6f87b121590f66)

この結果、[2.2.7](https://github.com/spring-projects/spring-data-commons/blob/2.2.7.RELEASE/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java#L112)まではアプリ起動時に行われていたConverterの登録処理が

```java:2.2.7のDomainClassConverter
	public void setApplicationContext(ApplicationContext context) {

		this.repositories = new Repositories(context);

		this.toEntityConverter = Optional.of(new ToEntityConverter(this.repositories, this.conversionService));
		this.toEntityConverter.ifPresent(it -> this.conversionService.addConverter(it));

		this.toIdConverter = Optional.of(new ToIdConverter());
		this.toIdConverter.ifPresent(it -> this.conversionService.addConverter(it));
	}
```

[2.2.8](https://github.com/spring-projects/spring-data-commons/blob/2.2.8.RELEASE/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java#L110)では遅延実行されることになり、

```java:2.2.8のDomainClassConverter
	public void setApplicationContext(ApplicationContext context) {

		this.repositories = Lazy.of(() -> {

			Repositories repositories = new Repositories(context);

			this.toEntityConverter = Optional.of(new ToEntityConverter(repositories, conversionService));
			this.toEntityConverter.ifPresent(it -> conversionService.addConverter(it));

			this.toIdConverter = Optional.of(new ToIdConverter(repositories, conversionService));
			this.toIdConverter.ifPresent(it -> conversionService.addConverter(it));

			return repositories;
		});
	}
```

`this.repositories`は`setApplicationContext`メソッドの上にある`getConverter`メソッドが呼び出されたとき[^1]に遅延初期化されるものの、
もうおわかりだろう・・・
**誰も！！getConverterメソッドを呼んでいないのである！！！**

[^1]: `getConverter`メソッドは`convert`メソッド（このメソッドはSpringのコア機能）が呼ばれたときに使われる。

どうにか呼び出す方法はないものかと小一時間悩んでも解決できなかったので最新だと修正されてるか確認したら初めに挙げたコミットを見つけたというオチです。

# あとがき

というわけで今回はSpring Dataの修正によりはまったというお話でした。一つバグを直したら別のところが壊れたという状況ですね。
ただ遅延初期化を導入したコミット自体は:white_check_mark:となっており、「テストしてない部分は壊れててもわからない」ということはSpringのように規模が大きくなってくると起こりえるのだなと自戒も含めて思いました。