Spring Data API のエクステンション
ユーザーにはリレーショナルデータベースアクセスに Hibernate ORM with Panache の使用が勧められていますが、Quarkus は spring-data-jpa
エクステンションの形式で Spring Data JPA リポジトリーの互換性レイヤーを提供します。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.8
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Git リポジトリーのクローンを作成: git clone https://github.com/quarkusio/quarkus-quickstarts.git
、または アーカイブ をダウンロードします。
ソリューションは spring-data-jpa-quickstart
ディレクトリ にあります。
Maven プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=spring-data-jpa-quickstart"
このコマンドは、Maven プロジェクトを生成し、spring-data-jpa
エクステンションをインポートします。
すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに sspring-data-jpa
エクステンションを追加することができます。
./mvnw quarkus:add-extension -Dextensions="spring-data-jpa"
これにより、 pom.xml
に以下が追加されます:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-data-jpa")
エンティティーの定義
このガイドのコース全体を通して、次の JPA エンティティーが使用されます。
package org.acme.spring.data.jpa;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Fruit {
@Id
@GeneratedValue
private Long id;
private String name;
private String color;
public Fruit() {
}
public Fruit(String name, String color) {
this.name = name;
this.color = color;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
データベースアクセスプロパティーの設定
次のプロパティーを application.properties
に追加して、ローカル PostgreSQL インスタンスへのアクセスを設定します。
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql:quarkus_test
quarkus.datasource.jdbc.max-size=8
quarkus.datasource.jdbc.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
この設定は、PostgreSQL がローカルで実行されることを前提としています。
これを実現する非常に簡単な方法は、次の Docker コマンドを使用することです。
docker run -it --rm=true --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:14.1
別の設定を使用する場合は、適宜 application.properties
を変更してください。
データの準備
Quarkus で Spring DataJ PA の一部の機能を簡単に示すために、次のコンテンツを src/main/resources/import.sql
という名前の新しいファイルに追加して、データベースにテストデータを挿入する必要があります。
INSERT INTO fruit(id, name, color) VALUES (1, 'Cherry', 'Red');
INSERT INTO fruit(id, name, color) VALUES (2, 'Apple', 'Red');
INSERT INTO fruit(id, name, color) VALUES (3, 'Banana', 'Yellow');
INSERT INTO fruit(id, name, color) VALUES (4, 'Avocado', 'Green');
INSERT INTO fruit(id, name, color) VALUES (5, 'Strawberry', 'Red');
Hibernate ORM は、アプリケーションの起動時にこれらのクエリーを実行します。
Users can also use a file named data.sql in addition to import.sql
|
リポジトリーの定義
次に、Fruit
へのアクセスに使用されるリポジトリーを定義します。典型的な Spring Data の方法で、次のようなリポジトリーを作成します。
package org.acme.spring.data.jpa;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface FruitRepository extends CrudRepository<Fruit, Long> {
List<Fruit> findByColor(String color);
}
上記の FruitRepository
は、Spring Data の org.springframework.data.repository.CrudRepository
を拡張します。これは、後者のすべてのメソッドが FruitRepository
で使用できることを意味します。さらに、指定された色に一致するすべての Fruit エンティティーを返すことを目的とした findByColor
が定義されています。
Jakarta RESTリソースの更新
リポジトリができたので、次は FruitRepository
を利用する Jakarta REST リソースを作成します。 次の内容で FruitResource
を作成します:
package org.acme.spring.data.jpa;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import java.util.List;
import java.util.Optional;
@Path("/fruits")
public class FruitResource {
private final FruitRepository fruitRepository;
public FruitResource(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
@GET
public Iterable<Fruit> findAll() {
return fruitRepository.findAll();
}
@DELETE
@Path("{id}")
public void delete(long id) {
fruitRepository.deleteById(id);
}
@POST
@Path("/name/{name}/color/{color}")
public Fruit create(String name, String color) {
return fruitRepository.save(new Fruit(name, color));
}
@PUT
@Path("/id/{id}/color/{color}")
public Fruit changeColor(Long id, String color) {
Optional<Fruit> optional = fruitRepository.findById(id);
if (optional.isPresent()) {
Fruit fruit = optional.get();
fruit.setColor(color);
return fruitRepository.save(fruit);
}
throw new IllegalArgumentException("No Fruit with id " + id + " exists");
}
@GET
@Path("/color/{color}")
public List<Fruit> findByColor(String color) {
return fruitRepository.findByColor(color);
}
}
FruitResource
は、Fruit
で CRUD 操作を実行するために使用できるいくつかの REST エンドポイントを提供するようになりました。
Spring Web に関する注意
QuarkusはSpring Web コントローラを使用したRESTエンドポイント定義をサポートしているため、Jakarta RESTリソースはSpring Webコントローラで代用することも可能です。詳細については、 Spring Webガイド を参照してください。
テストの更新
FruitRepository
の機能をテストするには、FruitResourceTest
のコンテンツを次のように更新します。
package org.acme.spring.data.jpa;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.IsNot.not;
@QuarkusTest
class FruitResourceTest {
@Test
void testListAllFruits() {
//List all, should have all 3 fruits the database has initially:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
containsString("Cherry"),
containsString("Apple"),
containsString("Banana")
);
//Delete the Cherry:
given()
.when().delete("/fruits/1")
.then()
.statusCode(204)
;
//List all, cherry should be missing now:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Banana")
);
//Create a new Fruit
given()
.when().post("/fruits/name/Orange/color/Orange")
.then()
.statusCode(200)
.body(containsString("Orange"))
.body("id", notNullValue())
.extract().body().jsonPath().getString("id");
//List all, Orange should be present now:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Orange")
);
}
@Test
void testFindByColor() {
//Find by color that no fruit has
given()
.when().get("/fruits/color/Black")
.then()
.statusCode(200)
.body("size()", is(0));
//Find by color that multiple fruits have
given()
.when().get("/fruits/color/Red")
.then()
.statusCode(200)
.body(
containsString("Apple"),
containsString("Strawberry")
);
//Find by color that matches
given()
.when().get("/fruits/color/Green")
.then()
.statusCode(200)
.body("size()", is(1))
.body(containsString("Avocado"));
//Update color of Avocado
given()
.when().put("/fruits/id/4/color/Black")
.then()
.statusCode(200)
.body(containsString("Black"));
//Find by color that Avocado now has
given()
.when().get("/fruits/color/Black")
.then()
.statusCode(200)
.body("size()", is(1))
.body(
containsString("Black"),
containsString("Avocado")
);
}
}
テストは、以下を発行することで簡単に実行できます。
./mvnw test
./gradlew test
アプリケーションをパッケージ化して実行する
Quarkus 開発モードは、他の Quarkus エクステンションと同じように、定義済みリポジトリーで機能し、開発サイクル中の生産性を大幅に向上させます。アプリケーションは、通常どおり、以下を使用して開発モードで起動できます。
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
アプリケーションをネイティブバイナリーとして実行
もちろん、このガイド に記載された手順で、ネイティブ実行可能ファイルを作成することもできます。
サポートされている Spring Data JPA 機能
Quarkus は現在、Spring Data JPA の機能のサブセット、つまり最も有用で最も一般的に使用される機能をサポートしています。
このサポートで重要なのは、すべてのリポジトリー生成がビルド時に行われるため、サポートされているすべての機能がネイティブモードで正しく機能することを保証することです。さらに、開発者はビルド時に、リポジトリーメソッド名を適切な JPQL クエリーに変換できるかどうかを認識しています。これは、メソッド名がエンティティーの一部ではないフィールドを使用する必要があることを示している場合、開発者はビルド時に関連するエラーを受け取ることも意味します。
サポート対象
次のセクションでは、Spring Data JPA でサポートされている最も重要な機能について説明しています。
自動リポジトリー実装生成
次の SpringData リポジトリーのいずれかを拡張するインターフェイスが自動的に実装されます。
-
org.springframework.data.repository.Repository
-
org.springframework.data.repository.CrudRepository
-
org.springframework.data.repository.ListCrudRepository
-
org.springframework.data.repository.PagingAndSortingRepository
-
org.springframework.data.repository.ListPagingAndSortingRepository
-
org.springframework.data.jpa.repository.JpaRepository
生成されたリポジトリーも Bean として登録されるため、他の Bean に注入できます。さらに、データベースを更新するメソッドには、自動的に @Transactional
のアノテーションが付けられます。
リポジトリー定義の微調整
これにより、ユーザー定義のリポジトリーインターフェイスは、インターフェイスを拡張することなく、サポートされている Spring Data リポジトリーインターフェイスのメソッドのみ選択できます。これは、たとえばリポジトリーが CrudRepository
のいくつかのメソッドを使用する必要があるが、そのインターフェイスのメソッドの完全なリストを公開することが望ましくない場合に特に役立ちます。
たとえば PersonRepository
は、CrudRepository
を拡張するべきではないが、そのインターフェイスで定義されている save
メソッドと findById
メソッドを使用したいと想定します。この場合、PersonRepository
は次のようになります。
package org.acme.spring.data.jpa;
import org.springframework.data.repository.Repository;
public interface PersonRepository extends Repository<Person, Long> {
Person save(Person entity);
Optional<Person> findById(Person entity);
}
リポジトリーフラグメントを使用した個々のリポジトリーのカスタマイズ
リポジトリーは、追加機能で強化したり、サポートされている Spring Data リポジトリーのメソッドのデフォルト実装をオーバーライドしたりできます。これを分かりやすく示しているのが以下の例です。
リポジトリーフラグメントは次のように定義されます。
public interface PersonFragment {
// custom findAll
List<Person> findAll();
void makeNameUpperCase(Person person);
}
そのフラグメントの実装は次のようになります。
import java.util.List;
import io.quarkus.hibernate.orm.panache.runtime.JpaOperations;
public class PersonFragmentImpl implements PersonFragment {
@Override
public List<Person> findAll() {
// do something here
return (List<Person>) JpaOperations.findAll(Person.class).list();
}
@Override
public void makeNameUpperCase(Person person) {
person.setName(person.getName().toUpperCase());
}
}
この場合、使用される実際の PersonRepository
インターフェイスは次のようになります。
public interface PersonRepository extends JpaRepository<Person, Long>, PersonFragment {
}
派生クエリーメソッド
Spring Data の規則に従うリポジトリーインターフェイスのメソッドは、(後でリストされているサポート対象外のケースに該当しない限り) 自動的に実装できます。これは、次のようなメソッドがすべて機能することを意味します。
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByName(String name);
Person findByNameBySsn(String ssn);
Optional<Person> findByNameBySsnIgnoreCase(String ssn);
boolean existsBookByYearOfBirthBetween(Integer start, Integer end);
List<Person> findByName(String name, Sort sort);
Page<Person> findByNameOrderByJoined(String name, Pageable pageable);
List<Person> findByNameOrderByAge(String name);
List<Person> findByNameOrderByAgeDesc(String name, Pageable pageable);
List<Person> findByAgeBetweenAndNameIsNotNull(int lowerAgeBound, int upperAgeBound);
List<Person> findByAgeGreaterThanEqualOrderByAgeAsc(int age);
List<Person> queryByJoinedIsAfter(Date date);
Collection<Person> readByActiveTrueOrderByAgeDesc();
Long countByActiveNot(boolean active);
List<Person> findTop3ByActive(boolean active, Sort sort);
Stream<Person> findPersonByNameAndSurnameAllIgnoreCase(String name, String surname);
}
ユーザー定義のクエリー
@Query
アノテーションに含まれるユーザー提供のクエリー。たとえば、次のようなものはすべて機能します。
public interface MovieRepository extends CrudRepository<Movie, Long> {
Movie findFirstByOrderByDurationDesc();
@Query("select m from Movie m where m.rating = ?1")
Iterator<Movie> findByRating(String rating);
@Query("from Movie where title = ?1")
Movie findByTitle(String title);
@Query("select m from Movie m where m.duration > :duration and m.rating = :rating")
List<Movie> withRatingAndDurationLargerThan(@Param("duration") int duration, @Param("rating") String rating);
@Query("from Movie where title like concat('%', ?1, '%')")
List<Object[]> someFieldsWithTitleLike(String title, Sort sort);
@Modifying
@Query("delete from Movie where rating = :rating")
void deleteByRating(@Param("rating") String rating);
@Modifying
@Query("delete from Movie where title like concat('%', ?1, '%')")
Long deleteByTitleLike(String title);
@Modifying
@Query("update Movie m set m.rating = :newName where m.rating = :oldName")
int changeRatingToNewName(@Param("newName") String newName, @Param("oldName") String oldName);
@Modifying
@Query("update Movie set rating = null where title =?1")
void setRatingToNullForTitle(String title);
@Query("from Movie order by length(title)")
Slice<Movie> orderByTitleLength(Pageable pageable);
}
@Modified
でアノテーションが付けられたすべてのメソッドには、自動的に`@Transactional` でアノテーションが付けられます。
Quarkus では、パラメーター名が (生成されたプロジェクトでデフォルトでアクティブになっている) バイトコードにコンパイルされている場合、@Param はオプションです。
|
命名戦略
Hibernate ORM は、物理的な命名戦略と暗黙的な命名戦略を使用してプロパティー名をマップします。Spring Boot のデフォルトの命名戦略を使用する場合は、次のプロパティーを設定する必要があります。
quarkus.hibernate-orm.physical-naming-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
quarkus.hibernate-orm.implicit-naming-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
その他の例
多様な例のリストは、Quarkus ソースコード内の 統合テスト にあります。
現在サポートされていないもの
-
org.springframework.data.repository.query.QueryByExampleExecutor
インターフェイスのメソッド。そのいずれかが呼び出されると、ランタイム例外が出力されます。 -
QueryDSL のサポート。QueryDSL 関連リポジトリーの実装を生成する試みは行われません。
-
Using
org.springframework.data.jpa.repository.JpaSpecificationExecutor
-
コードベース内のすべてのリポジトリーインターフェイスのベースリポジトリーをカスタマイズします。
-
Spring Data JPA では、これは
org.springframework.data.jpa.repository.support.SimpleJpaRepository
を拡張するクラスを登録することによって行われますが、Quarkus ではこのクラスはいっさい使用されません (必要な配置設定はすべてビルド時に行われるため)。将来、同様のサポートが Quarkus に追加される可能性があります。
-
-
java.util.concurrent.Future
と、それをリポジトリーメソッドのリターンタイプとして拡張するクラスを使用します。 -
@Query
を使用する場合のネイティブクエリーと名前付きクエリー -
EntityInformation
経由の エンティティーステート検出戦略. -
org.springframework.data.jpa.repository.Lock
の使用
Quarkus チームは、JPA と Reactive の間のギャップを埋めるために、さまざまな代替案を模索しています。
重要な技術的注意点
Quarkus での Spring サポートは、Spring アプリケーションコンテキストを開始せず、Spring インフラストラクチャークラスも実行されないことに注意してください。Spring クラスとアノテーションは、メタデータの読み取りにのみ使用されるか、ユーザーコードメソッドのリターンタイプまたはパラメータータイプとして使用されます。