1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JPAからR2DBCに切り替え作業

Posted at

更新履歴

日付 内容
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

セットアップ

  1. build.gradleに依存関係を設定
  2. application.ymlの修正
  3. 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のレスポンスが遅くなってしまった。
チューニングしていきたい。。。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?