更新履歴
日付 | 内容 |
---|---|
2021.03.09 | 初版 |
はじめに
ORMを SpringDataJpa
で実装していたが、WebFluxに足を踏み入れたので、
思い切って SpringDataR2DBC
にも入門してみた。
とりあえずカスタマイズ/チューニングは無視して、動かせるようにした経過を記録しておく。
- FW
- Spring Boot 2.4.3
- Libraries
- spring-boot-starter-webflux
- spring-boot-starter-data-r2dbc
- JDK
- AdoptOpenJdk 11.0.6
- Build
- Gradle 6.8.3
- IDE
- IntelliJ IDEA Community 2020.3
- Docker
- mysql:8.0.23
R2DBC
セットアップ
- build.gradleに依存関係を設定
- application.ymlの修正
- Javaコード修正
build.gradle
build.gradle
dependencies {
// JPAをコメントアウトしてR2DBCを追加
//api("org.springframework.boot:spring-boot-starter-data-jpa")
api("org.springframework.boot:spring-boot-starter-data-r2dbc")
// R2DBC用の実装で必要
runtimeOnly("dev.miku:r2dbc-mysql")
runtimeOnly("mysql:mysql-connector-java")
}
application.yml
application.yml
spring:
r2dbc:
url: r2dbcs:mysql://localhost:3306/db
username: root
password: root
JPAの記述などは全て削除した
Configクラスの修正
BaseJpaConfig.java
@EntityScan
@EnableJpaRepositories
@EnableTransactionManagement(proxyTargetClass = true)
public abstract class BaseJpaConfig {
}
BaseR2dbcConfig.java
@EnableR2dbcRepositories
@EnableTransactionManagement(proxyTargetClass = true)
public abstract class BaseR2dbcConfig extends AbstractR2dbcConfiguration {
}
Dtoの修正
BaseTable.java(修正前)
@Accessors(chain = true)
@Data
@MappedSuperclass
public abstract class BaseTable {
@CreatedDate
@Column(name = "created_date_time")
private LocalDateTime createdDateTime;
@Column(name = "created_by")
private Integer createdBy;
@Version
@Column(name = "version")
private Integer version;
@Column(name = "deleted_flag")
private Boolean deletedFlag = Boolean.FALSE;
}
BaseTable.java(修正後)
@Accessors(chain = true)
@Data
public abstract class BaseTable<ID> implements Persistable<ID> {
@CreatedDate
@Column(value = "created_date_time")
private LocalDateTime createdDateTime;
@Column(value = "created_by")
private Integer createdBy;
@Version
@Column(value = "version")
private Integer version;
@Column(value = "deleted_flag")
private DeletedFlag deletedFlag = DeletedFlag.is_false;
@Transient
private boolean isNew;
@Getter
public enum DeletedFlag {
is_true,
is_false
}
}
論理削除フラグをTINYINT型で定義していて、JPAでは Boolean
型でマッピングしていたが、
R2DBC
では起動時にエラーがでていた。
なので思い切ってenum型に定義しなおしたところうまく動作した。
Datasourceの修正
TableSource.java(修正前)
@NoRepositoryBean
public interface TableSource<D extends BaseTable, ID> extends JpaRepository<D, ID>, JpaSpecificationExecutor<D> {
@Async
default CompletableFuture<D> insert(D entity, LocalDateTime localDateTime) {
entity.setCreatedDateTime(localDateTime);
entity.setVersion(0);
return CompletableFuture.supplyAsync(() -> save(entity));
}
@Async
default CompletableFuture<D> logicalDelete(D entity, LocalDateTime localDateTime) {
entity.setDeletedDateTime(localDateTime);
entity.setDeletedFlag(true);
return CompletableFuture.supplyAsync(() -> save(entity));
}
}
TableSource.java(修正後)
@NoRepositoryBean
public interface TableSource<D extends BaseTable, ID> extends ReactiveSortingRepository<D, ID> {
default Mono<D> insert(D entity, LocalDateTime localDateTime) {
entity.setCreatedDateTime(localDateTime);
entity.setVersion(null);
entity.setNew(true);
return this.save(entity);
}
default Mono<D> logicalDelete(D entity, LocalDateTime localDateTime) {
entity.setDeletedDateTime(localDateTime);
entity.setDeletedFlag(DeletedFlag.is_true);
entity.setNew(false);
return this.save(entity);
}
}
サンプルソース
1件取得
StoreRepositoryImpl.java
public Mono<StoreEntity> findBy(StoreId storeId) {
return this.findDto(storeId)
.map(this::convertStoreEntity);
}
private Mono<Store> findDto(StoreId storeId) {
return this.source.findById(storeId.intValue())
.filter(BaseTable::isExists)
// 404で返却するためのエラーを検討
.switchIfEmpty(Mono.error(new RuntimeException("店舗が存在しません")));
}
1件更新
StoreRepositoryImpl.java
public Mono<StoreId> modify(StoreEntity entity, LocalDateTime receptionTime) {
return this.findDto(entity.getStoreId())
.map(dto -> this.attach(dto, entity))
.flatMap(dto -> this.source.update(dto, receptionTime))
.map(Store::getId)
.map(StoreId::new);
}
private Mono<Store> findDto(StoreId storeId) {
return this.source.findById(storeId.intValue())
.filter(BaseTable::isExists)
// 404で返却するためのエラーを検討
.switchIfEmpty(Mono.error(new RuntimeException("店舗が存在しません")));
}
private Store attach(Store dto, StoreEntity entity) {
var storeId = Optional.ofNullable(entity.getStoreId())
.map(StoreId::intValue)
.orElse(null);
var store = Optional.of(dto).orElse(new Store());
return store
.setId(storeId)
.setName(entity.getName());
}
Monoをchainする場合、flatMap
で繋ぐ
つまづいた箇所
ReactiveCrudRepository
にentity1件検索のfindOneがない😅
絞り込み検索が Mono.filter()
以外あるかわからない😅
今後の取り組み
CompletableFuture
から Mono
, Flux
を動かせるように変更しただけなので、APIのレスポンスが遅くなってしまった。
チューニングしていきたい。。。