Spring FirestoreReactiveRepository FireStoreのカラムに「_」がある場合

# FirestoreReactiveRepository
com.google.cloud.spring.data.firestore.FirestoreReactiveRepositoryは、Spring Dataの一部として提供されるインターフェースであり、Google Cloud Firestoreデータベースに対してリアクティブな操作を行うためのリポジトリのクラスです。

Google Cloud Firestoreデータベースに対する操作を簡単に行うためのメソッドが用意されており、特別に処理を記述することなくCRUDやOrderByなどを利用することができます。


## 利用する例
FireStoreにこんなテーブルがある場合

```com.example.entity.UserDocument.java
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の操作が可能

```com.example.repository.UserRepository.java
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を作成

```com.example.repository.CustomUserRepository.java
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);
}
```

実態部分の処理を作成

```com.example.repository.CustomUserRepositoryImpl.java
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元に上で作成したクラスを追加してメソッドも記述

```com.example.repository.UserRepository.java
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つでよくなります。

## サービスクラスの例
サービスクラスの例も載せておきます。

```com.example.service.UserService.java
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);	            
}
```

処理実装部分

```com.example.service.UserServiceImpl.java
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として使いたいので最後に変換をかけています。