FirestoreReactiveRepository
com.google.cloud.spring.data.firestore.FirestoreReactiveRepositoryは、Spring Dataの一部として提供されるインターフェースであり、Google Cloud Firestoreデータベースに対してリアクティブな操作を行うためのリポジトリのクラスです。
Google Cloud Firestoreデータベースに対する操作を簡単に行うためのメソッドが用意されており、特別に処理を記述することなくCRUDやOrderByなどを利用することができます。
利用する例
FireStoreにこんなテーブルがある場合
package com.example.entity;
import com.google.cloud.firestore.annotation.DocumentId;
import com.google.cloud.spring.data.firestore.Document;
@Document(collectionName = "users")
public class UserDocument {
@DocumentId private String id;
private Integer user_category_id;
private String name;
# 略
こういうふうに書くだけでFireStoreの操作が可能
pachage com.example.repository;
import com.google.cloud.spring.data.firestore.FirestoreReactiveRepository;
import org.springframework.data.domain.Pageable;
import com.example.entity.UserDocument;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface UserRepository extends FirestoreReactiveRepository<UserDocument> {
// IDを指定して取得
Mono<UserDocument> findById(Integer id);
// nameを指定して取得
Mono<UserDoccument> findByName(String Name);
// namaeを指定してページングて取得
Flux<UserDoccument> findAllByName(String Name, Pageable pageable);
// ID順ページングして取得
Flux<UserDoccument> findAllByOrderById(Pageable pageable);
カラム名をキャメライズして書くだけでそれに準じた動作を行うことができます。
カラム名に「_」がある場合
こんな風に書けるけど実行時エラーになります。
Flux<UserDocument> findAllByOrderByUser_category_idAsc(Pageable pageable);
@Field や @PropertyName で 「_」なしのプロパティ名にマッピングしても null になったりするので使えません。
SpringとFireStoreは相性が悪いのかも、、、、
「_」があるカラムのために別クラスを作成
別の処理を定義してこの不具合を回避します。
user_category_id で order する例です。
- com.example.repository.CustomUserRepository の名前でinterfaceを作成
package com.example.repository;
import org.springframework.data.domain.Pageable;
import com.example.entity.UserDocument;
import reactor.core.publisher.Flux;
public interface CustomuUserRepository {
// user_category_id でソートして取得
Flux<UserDocument> findAllByOrderByUserCategoryIdAsc(Pageable pageable);
}
実態部分の処理を作成
package com.example.repository;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import com.google.api.core.ApiFuture;
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.QuerySnapshot;
import reactor.core.publisher.Flux;
import com.example.entity.UserDocument;
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@Autowired
private Firestore firestore;
@Override
public Flux<UserDocument> findAllByOrderByUserCategoryIdAsc(Pageable pageable) {
// order するクエリを定義 Pageableでページング対応
com.google.cloud.firestore.Query query = firestore.collection("users")
.orderBy("user_category_id")
.offset((int) pageable.getOffset())
.limit(pageable.getPageSize());
// クエリを非同期的に実行し、ApiFuture<QuerySnapshot>を返す
ApiFuture<QuerySnapshot> querySnapshot = query.get();
// 次に定義するgetUsersFromQuerySnapshot(querySnapshot)メソッドを使用して、
// ApiFuture<QuerySnapshot>を処理し、QuerySnapshotからUserオブジェクトのリストを取得
// 取得したリストをFlux<UserDocument>に変換
return Flux.fromIterable(getUsersFromQuerySnapshot(querySnapshot));
}
// 非同期QuerySnapshotをListに変換する処理 try-catch が必要なので別メソッドに分ける
private List<UserDocument> getUsersFromQuerySnapshot(ApiFuture<QuerySnapshot> querySnapshot) {
try {
return querySnapshot.get() // 非同期クエリの結果を同期的に取得
.getDocuments() // クエリ結果のドキュメントを取得
.stream()
.map(document -> document.toObject(UserDocument.class)) // 取得したドキュメントをUserDocumentクラスのインスタンスに変換
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to fetch users from Firestore", e);
}
}
}
ちょっと長くなるけど非同期にFireStoreからデータをソートして取得した後に Flux に変換して返す処理を作成
作成したクラスを元の Repository クラスに統合
extends元に上で作成したクラスを追加してメソッドも記述
pachage com.example.repository;
import com.google.cloud.spring.data.firestore.FirestoreReactiveRepository;
import org.springframework.data.domain.Pageable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import com.example.entity.UserDocument;
public interface UserRepository extends FirestoreReactiveRepository<UserDocument>, CustomuUserRepository /*ここを追加*/ {
// IDを指定して取得
Mono<UserDocument> findById(Integer id);
// nameを指定して取得
Mono<UserDoccument> findByName(String Name);
// namaeを指定してページングて取得
Flux<UserDoccument> findAllByName(String Name, Pageable pageable);
// ID順ページングして取得
Flux<UserDoccument> findAllByOrderById(Pageable pageable);
// ここを追加
// user_category_id でソートして取得
Flux<UserDocument> findAllByOrderByUserCategoryIdAsc(Pageable pageable);
これでサービスクラスから利用するレポジトリのクラスは1つでよくなります。
サービスクラスの例
サービスクラスの例も載せておきます。
package com.example.service;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.example.entity.UserDocument;
public interface UserService {
// IDを指定して取得
public UserDocument get(String id);
// nameを指定して取得
public UserDocument findByName(String name);
// IDに順ページングして取得
public Page<UserDocument> findAllByOrderById(Pageable pageable);
// user_category_idに順ページングして取得
public Page<UserDocument> findAllByOrderByUserCategoryIdAsc(Pageable pageable);
}
処理実装部分
package com.example.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.example.entity.UserDocument;
import com.example.repository.UserRepository;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserRepository repository;
@Override
public UserDocument get(String id) {
return repository.findById(id).block();
}
@Override
public UserDocument findByName(String name) {
return repository.findByName(name).block();
}
@Override
public Page<UserDocument> findAllByOrderById(Pageable pageable){
return repository.findAllByOrderById(pageable)
.collectList()
.zipWith(repository.count())
.map(p -> new PageImpl<>(p.getT1(), pageable, p.getT2())).block();
}
@Override
public Page<UserDocument> findAll(Pageable pageable){
return repository
.findAllByOrderByUserCategoryIdAscc(pageable)
.collectList()
.zipWith(repository.count())
.map(p -> new PageImpl<>(p.getT1(), pageable, p.getT2())).block();
}
}
Controller側でPageとして使いたいので最後に変換をかけています。