Spring Data JPAとSpring Data JDBCの共存

現在、Spring Data JPAを利用してて、JPAからの脱却をするとなると現実的な移行方法を考えたときに、Spring Data JDBCが移行先かなと思い検証を実施しました。 レポジトリは、spring-data-jpa-and-jdbcです。

今回の環境

- Java 21
- Spring Boot 3.4.0

検証手順

- Spring Data JPAを利用したアプリケーションの実装
- Spring Data JDBCを依存関係に追加し、Spring Data JPAで実装されている処理と同等の処理を追加

検証

Spring Data JPAを利用したアプリケーションの実装

build.gradleに以下の依存関係を追加します。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
}

次に、以下のER図をものにJPA エンティティを実装します。

ER図

@Entity
@Table(name = "shop")
@EntityListeners(AuditingEntityListener.class)
public class JpaShop {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @CreatedDate
    @ReadOnlyProperty
    private Instant createdAt;

    @LastModifiedDate
    @ReadOnlyProperty
    private Instant updatedAt;

    @OneToMany(mappedBy = "shop")
    private List<JpaUser> users;

   // getter, setterは省略
}
@Entity
@Table(name = "user")
@EntityListeners(AuditingEntityListener.class)
public class JpaUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private Integer age;

    @CreatedDate
    @ReadOnlyProperty
    private Instant createdAt;

    @LastModifiedDate
    @ReadOnlyProperty
    private Instant updatedAt;

    @ManyToOne
    @JoinColumn(name = "shop_id")
    private JpaShop shop;

   // getter, setterは省略
}

設定の追加

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
        basePackages = {
                "com.b1a9idps.spring_data_jpa_and_jdbc.application.repository.jpa"
        }
)
@EnableJpaAuditing
public class JpaConfig {
}

あとは、JpaRepository.javaをextendsしたインターフェースを実装して、データアクセスします。

Spring Data JDBCの導入

まず、build.gradleにSpring Data JDBCを追加します

dependencies {
    // 追加
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
}

次に、先に実装したJPAエンティティと同等のクラスを実装します。

@Table("shop")
public class JdbcShop {
    @Id
    private Integer id;

    private String name;

    @MappedCollection(idColumn = "shop_id", keyColumn = "shop_id")
    private List<JdbcUser> users;

    @CreatedDate
    @ReadOnlyProperty
    private LocalDateTime createdAt;

    @LastModifiedDate
    @ReadOnlyProperty
    private LocalDateTime updatedAt;

   // getter, setterは省略
@Table("user")
public class JdbcUser {
    @Id
    private Integer id;

    private String name;

    private Integer age;

    private Integer shopId;

    @CreatedDate
    @ReadOnlyProperty
    private Instant createdAt;

    @LastModifiedDate
    @ReadOnlyProperty
    private Instant updatedAt;

   // getter, setterは省略

設定の追加

@Configuration(proxyBeanMethods = false)
@EnableJdbcRepositories(
        basePackages = {
                "com.b1a9idps.spring_data_jpa_and_jdbc.application.repository.jdbc"
        }
)
@EnableJdbcAuditing
public class JdbcConfig extends AbstractJdbcConfiguration {
}

あとは、ListPagingAndSortingRepository.javaをextendsしたインターフェースを実装して、データアクセスします。

Spring Data JPAとSpring Data JDBCを共存させるときの注意点

1. JDBCのエンティティには spring-data-relational@Tableを付与する

JDBCのエンティティには spring-data-relational@Tableを付与します。付与しない場合は、テーブルにマッピングするクラスを認識されません。

2. エンティティに付与する @Idアノテーションを間違えない

わかりづらいですが、Spring Data JPAのエンティティには、jakarta.persistence-api@Idを付与し、Spring Data JDBCのエンティティには、spring-data-commons@Idを付与します。

まとめ

Spring Data JDBCははじめて触ったが、Spring Data JDBCは、シンプルで規約に従ったデータ操作を優先しており、JPAのようなJOINや複雑なエンティティリレーションや動的クエリを扱うことを目的としてない設計のため、単体ではあまり使い物にならなず、クエリビルダーとの併用が必須だなと感じた。かなり薄い作りなので学習コストは低いように感じました。

技術選定としては、Spring Data JDBC + JOOQ(クエリビルダーとして)が個人的にはしっくりきました。