前回のエントリーでも少し振れましたが、Spring Data JPAではこのように基底リポジトリインターフェイスを集約した JpaRepository というインターフェイスを提供しています。Spring Data JDBCではこのようなインターフェイスは提供していませんが、あった方が便利だし拡張もしやすいので、まずはこれを作りました。
そして独自拡張となる query メソッドを定義して、Spring Data JDBCの基本機能に加えて query メソッドを使えるように定義しておきます。
全フィールドをprotectedにするとか、FactoryBeanだけでなくFactoryをカスタマイズ可能にする、みたいなことをSpring Data JDBCの開発者にお願いしたら通るもんなんですかね。もうちょっと真面目に開発・メンテナンスするつもりになったら、issueでも立てて相談してみます。
さて、次はどうしようかなと考えているときに気づいたのですが、わざわざSpring Dataを自作しなくとも、Spring Data JDBCに対して上に書いた2つの方法を使って拡張することができるのではないでしょうか。ここまでSpring Data JDBCのコードを読んできて、どうすれば拡張ができるのかが大体分かるようになってきました。また逆に、自作Spring Dataの DefaultRepository を実装するために、延々とSpring Data JDBCから処理をコピーしてくるというのも大変なことです。
そこで次回はいったん自作Spring Dataという方向を止め、Spring Data JDBCを拡張するという方向に進んでみます。
public SimpleJdbcRepository(JdbcAggregateOperations entityOperations, PersistentEntity<T, ?> entity,
JdbcConverter converter) {
Assert.notNull(entityOperations, "EntityOperations must not be null");
Assert.notNull(entity, "Entity must not be null");
this.entityOperations = entityOperations;
this.entity = entity;
this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext());
}
@Autowiredpublicvoid setMappingContext(RelationalMappingContext mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null");
super.setMappingContext(mappingContext);
this.mappingContext = mappingContext;
}
@Overridepublic <T> Iterable<T> findAll(Class<T> domainType) {
Assert.notNull(domainType, "Domain type must not be null");
Iterable<T> all = accessStrategy.findAll(domainType);
return triggerAfterConvert(all);
}
Spring Data JDBCのコードを読むと JdbcRepositoryFactory や JdbcRepositoryFactoryBean というクラスでRepositoryのinterfaceに対する実装を提供しているようなのですが、何をどうしたら自前のファクトリクラスが呼ばれるようになるのかが分かりません。spring-data-commons というSpring Dataを作るための共通モジュールがあるものの、これの使い方がよく分かりません。
Spring Data JDBCの META-INF/spring.factories というファイルに org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory という記述があったので、これか!? と思って真似てみたものの、やっぱり自前のファクトリクラスが呼ばれることはありませんでした。
2022-12-23T00:32:37.874+09:00 ERROR 1285 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'empRepository' defined in ninja.cero.data.sql.app.EmpRepository defined in @EnableSqlRepositories declared on App: Cannot invoke "Object.getClass()" because "target" is null
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1751) ~[spring-beans-6.0.2.jar:6.0.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.0.2.jar:6.0.2]
(略)
Caused by: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "target" is null
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:319) ~[spring-data-commons-3.0.0.jar:3.0.0]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279) ~[spring-data-commons-3.0.0.jar:3.0.0]
at org.springframework.data.util.Lazy.getNullable(Lazy.java:229) ~[spring-data-commons-3.0.0.jar:3.0.0]
(略)
% curl localhost:8080/test1
{"timestamp":"2022-12-21T11:55:49.153+00:00","path":"/test1","status":500,"error":"Internal Server Error","requestId":"9fdfe798-13"}
サーバ側のコンソールには次のように出力されています。
/error 2022-12-21T21:00:15.500676
/error 2022-12-21T21:00:16.538485
/error 2022-12-21T21:00:17.543379
/error 2022-12-21T21:00:18.549469
2022-12-21T21:00:18.565+09:00 ERROR 7455 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [53110703-1] 500 Server Error for HTTP GET "/test1"
reactor.core.Exceptions$RetryExhaustedException: Retries exhausted: 3/3
at reactor.core.Exceptions.retryExhausted(Exceptions.java:306) ~[reactor-core-3.5.0.jar:3.5.0]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler ninja.cero.example.webflux.retry.TestController#test1() [DispatcherHandler]
*__checkpoint ⇢ HTTP GET "/test1" [ExceptionHandlingWebHandler]
Original Stack Trace:
at reactor.core.Exceptions.retryExhausted(Exceptions.java:306) ~[reactor-core-3.5.0.jar:3.5.0]
at reactor.util.retry.RetryBackoffSpec.lambda$static$0(RetryBackoffSpec.java:68) ~[reactor-core-3.5.0.jar:3.5.0]
at reactor.util.retry.RetryBackoffSpec.lambda$null$4(RetryBackoffSpec.java:560) ~[reactor-core-3.5.0.jar:3.5.0]
(略)
Caused by: org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:337) ~[spring-webflux-6.0.2.jar:6.0.2]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 503 SERVICE_UNAVAILABLE from POST http://localhost:8080/error [DefaultWebClient]
Original Stack Trace:
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:337) ~[spring-webflux-6.0.2.jar:6.0.2]
(略)
curl localhost:8080/test4
{"timestamp":"2022-12-21T12:28:45.122+00:00","path":"/test4","status":500,"error":"Internal Server Error","requestId":"990c29b5-9"}
サーバ側のコンソールには次のように出力されています。
/error 2022-12-21T21:28:42.086369
Thread[reactor-http-nio-3,5,main] Error1 2022-12-21T21:28:42.087464 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error
/error 2022-12-21T21:28:43.094621
Thread[reactor-http-nio-3,5,main] Error1 2022-12-21T21:28:43.096406 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error
/error 2022-12-21T21:28:44.103211
Thread[reactor-http-nio-3,5,main] Error1 2022-12-21T21:28:44.104817 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error
/error 2022-12-21T21:28:45.107813
Thread[reactor-http-nio-3,5,main] Error1 2022-12-21T21:28:45.109541 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error
Thread[reactor-http-nio-3,5,main] Error2 2022-12-21T21:28:45.110772 reactor.core.Exceptions$RetryExhaustedException: Retries exhausted: 3/3
2022-12-21T21:28:45.125+09:00 ERROR 8812 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [990c29b5-9] 500 Server Error for HTTP GET "/test4"
reactor.core.Exceptions$RetryExhaustedException: Retries exhausted: 3/3
(略)
curl localhost:8080/test6
{"timestamp":"2022-12-21T13:01:31.209+00:00","path":"/test6","status":500,"error":"Internal Server Error","requestId":"9cb1d7f4-1"}
サーバ側のコンソールには次のように出力されています。
7 /error_503_404 2022-12-21T22:01:29.151704
Thread[reactor-http-nio-3,5,main] Error 2022-12-21T22:01:29.181986 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error_503_404
8 /error_503_404 2022-12-21T22:01:30.187425
Thread[reactor-http-nio-3,5,main] Error 2022-12-21T22:01:30.189003 org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from POST http://localhost:8080/error_503_404
9 /error_503_404 2022-12-21T22:01:31.196340
Thread[reactor-http-nio-3,5,main] Error 2022-12-21T22:01:31.198393 org.springframework.web.reactive.function.client.WebClientResponseException$NotFound: 404 Not Found from POST http://localhost:8080/error_503_404
2022-12-21T22:01:31.212+09:00 ERROR 12778 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [9cb1d7f4-1] 500 Server Error for HTTP GET "/test6"
org.springframework.web.reactive.function.client.WebClientResponseException$NotFound: 404 Not Found from POST http://localhost:8080/error_503_404
(略)