- はじめに
- Panache とは
- Panache の利用準備
- Entity の定義
- Entity の操作
- ページング
- クエリー
- Entity メソッド
- トランザクション
- ロック
- カスタムIDの利用
- リポジトリとしての利用
- PanacheEntity の静的メソッド一覧
はじめに
前回 Quarkus で JPA を使った簡単なアプリケーション作成を行いました。
今回は、Panache を使ったデータベースアクセスを見ていきます。
Panache とは
Panache は Quarkus に含まれ、簡易的なORMソリューションを提供します。
背後で Hibernate などの ORM を利用しますが、利用側からは ActiveRecord として扱えます。
Hibernate などの ORM は高度な機能を提供しますが、単純で一般的な利用用途でも定義が複雑化することがあります。
Panache は、このような単純な利用において、Entity の作成をより簡単にすることを目的としています。
Panache による Entity の定義は以下のようになります。
@Entity public class Person extends PanacheEntity { public String name; public LocalDate birth; public Status status; public static Person findByName(String name){ return find("name", name).firstResult(); } public static List<Person> findAlive(){ return list("status", Status.Alive); } public static void deleteStefs(){ delete("name", "Stef"); } }
- Entity の ID は親クラスで定義されるため不要
- カスタムID戦略が必要な場合は PanacheEntityBaseを拡張して定義する
- パブリックフィールドを使うことで getter/setter が不要
- getter / setter は自動生成され、呼び出し元も getter / setter コールに変換される
- 独自の getter / setter を定義することも可能
- スーパークラス PanacheEntity に定義された多くの静的メソッドで Entigy 操作が可能
Person.find("order by name")
やPerson.find("name", "stef")
のように必要のないクエリを記載する必要がない
Panache の利用準備
プロジェクトの作成や application.properties
の定義などは前回記事を参照とします。
差分として build.gradle
を以下のように変更します。
plugins { id 'java' id 'io.quarkus' version '0.23.2' } repositories { jcenter() } dependencies { implementation enforcedPlatform('io.quarkus:quarkus-bom:0.23.2') implementation 'io.quarkus:quarkus-resteasy-jsonb' implementation 'io.quarkus:quarkus-hibernate-orm-panache' implementation 'io.quarkus:quarkus-jdbc-postgresql' }
io.quarkus:quarkus-hibernate-orm
を io.quarkus:quarkus-hibernate-orm-panache
に変更するだけです。
Entity の定義
PanacheEntity を継承し、フィールドを public 宣言します。
@Entity public class Person extends PanacheEntity { public String name; public LocalDate birth; public Status status; }
Entity は以下のように利用できます。
Person person = new Person(); person.name = "Stef"; person.persist();
person.name
というフィールドへのアクセスは、コンパイル時に getter/setter へ変換されるため実行時にはカプセル化されます。
getter/setter は以下のように自身で定義すれば、そちらが利用されます。
@Entity public class Person extends PanacheEntity { // ... public String getName(){ return name.toUpperCase(); } public void setName(String name){ this.name = name.toLowerCase(); } }
Entity の操作
PanacheEntity に定義された静的メソッドにより Entity の操作ができます。
永続化
Person person = new Person(); person.name = "Stef"; person.birth = LocalDate.of(1910, Month.FEBRUARY, 1); person.status = Status.Alive; person.persist();
IDによる検索
person = Person.findById(personId);
一覧取得
List<Person> allPersons = Person.listAll();
List<Person> livingPersons = Person.list("status", Status.Alive);
ソート
List<Person> persons = Person.list(Sort.by("name").and("birth"));
件数取得
long countAll = Person.count(); long countAlive = Person.count("status", Status.Alive);
削除
if (person.isPersistent()) { person.delete(); } Person.delete("status", Status.Alive); Person.deleteAll();
Stream 操作
Stream<Person> persons = Person.streamAll();
List<String> namesButEmmanuels = persons
.map(p -> p.name.toLowerCase())
.filter(n -> ! "emmanuel".equals(n))
.collect(Collectors.toList());
ページング
ページングを行うには find
メソッドでクエリを作成し、このクエリを操作します。
PanacheQuery<Person> livingPersons = Person.find("status", Status.Alive); livingPersons.page(Page.ofSize(25));
PanacheQuery を生成し、1ページの件数を定義します。
ページの取得
List<Person> firstPage = livingPersons.list(); List<Person> secondPage = livingPersons.nextPage().list(); List<Person> page7 = livingPersons.page(Page.of(7, 25)).list();
件数の取得
int numberOfPages = livingPersons.pageCount(); int count = livingPersons.count();
メソッドチェーン
return Person.find("status", Status.Alive) .page(Page.ofSize(25)) .nextPage() .stream();
クエリー
find
メソッドで以下のようにクエリーを指定することができます。
Order.find("select distinct p from Person p left join fetch p.attributes");
from
キーワードから始めることで HQL を使うこともできます。
クエリパラメータは以下のようなプレースホルダで指定することができます。
Person.find("name = ?1 and status = ?2", "stef", Status.Alive);
Parameters
を指定してパラメータを構築して指定することができます。
Person.find("name = :name and status = :status", Parameters.with("name", "stef").and("status", Status.Alive));
またはマップとして指定することもできます。
Map<String, Object> params = new HashMap<>(); params.put("name", "stef"); params.put("status", Status.Alive); Person.find("name = :name and status = :status", params);
Entity メソッド
Panache では、Entity に対する操作は、対象の Entity に静的メソッドを追加して行うことを推奨しています。
以下のように静的メソッドを追加します。
@Entity public class Person extends PanacheEntity { // ... public static Person findByName(String name){ return find("name", name).firstResult(); } public static List<Person> findAlive(){ return list("status", Status.Alive); } }
トランザクション
RESTエンドポイントコントローラーのようなアプリケーションエントリポイントの境界でトランザクションを定義することが推奨されています。
@Path("/persons") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class PersonResource { @POST @Path("/") @Transactional public Long create(Person person) { person.persist(); return person.id; } }
アプリケーションサービスを定義する場合は以下のようになるでしょう。
@ApplicationScoped public class PersonService { @Transactional public Long create(String name) { Person person = new Person(); person.name = name; person.persist(); return person.id; } }
ロック
Panache では直接データベースのロックをサポートしませんが、Panache.getEntityManager()
で取得した EntityManager を使ってロックを行うことができます。
@Entity public class Person extends PanacheEntity { // ... public static Person findByIdForUpdate(Long id){ EntityManager entityManager = Panache.getEntityManager(); Person person = findById(id); entityManager.lock(person, LockModeType.PESSIMISTIC_WRITE); return person; } }
カスタムIDの利用
PanacheEntity は以下のような定義になっています。
@MappedSuperclass public abstract class PanacheEntity extends PanacheEntityBase { @Id @GeneratedValue public Long id; }
Entity 固有のカスタムIDを定義するには PanacheEntity
の代わりに PanacheEntityBase
を継承します。
@Entity public class Person extends PanacheEntityBase { @Id @SequenceGenerator( name = "personSequence", sequenceName = "person_id_seq", allocationSize = 1, initialValue = 4) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personSequence") public Integer id; //... }
リポジトリとしての利用
Panache では推奨していませんが、PanacheRepository を実装することで Repository を利用することもできます。
@ApplicationScoped public class PersonRepository implements PanacheRepository<Person> { public Person findByName(String name){ return find("name", name).firstResult(); } public List<Person> findAlive(){ return list("status", Status.Alive); } public void deleteStefs(){ delete("name", "Stef"); } }
Spring Data 風ですね。
以下のように EntityManager を使った処理もできます。
@ApplicationScoped public class PersonRepository implements PanacheRepository<Person> { @Inject EntityManager entityManager; public Person findByIdForUpdate(Long id){ Person person = findById(id); entityManager.lock(person, LockModeType.PESSIMISTIC_WRITE); return person; } }
PanacheEntity の静的メソッド一覧
Persist 系
メソッド |
---|
void persist() |
void persistAndFlush() |
void persist(Iterable<?> entities) |
void persist(Stream<?> entities) |
void persist(Object firstEntity, Object... entities) |
Delete 系
メソッド |
---|
void delete() |
long deleteAll() |
long delete(String query, Object... params) |
long delete(String query, Map<String, Object> params) |
long delete(String query, Parameters params) |
PanacheQuery 系
メソッド |
---|
PanacheQuery |
PanacheQuery |
PanacheQuery |
PanacheQuery |
PanacheQuery |
PanacheQuery |
PanacheQuery |
PanacheQuery |
List系
メソッド |
---|
List |
List |
List |
List |
List |
List |
List |
List |
Stream系
メソッド |
---|
Stream |
Stream |
Stream |
Stream |
Stream |
Stream |
Stream |
Stream |
Count 系
メソッド |
---|
long count() |
long count(String query, Object... params) |
long count(String query, Map<String, Object> params) |
long count(String query, Parameters params) |
その他
メソッド |
---|
T findById(Object id) |
void flush() |
boolean isPersistent() |
- 作者:Sam Newman
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/02/26
- メディア: 単行本(ソフトカバー)