JavaでMongoDBを操作する(SpringBoot)

MongoDBの導入手順は以下の記事に記述しています。

olafnosuke.hatenablog.com

JavaでMongoDBを操作する(SpringBoot)

依存関係の追加

build.gradleにspring-boot-starter-data-mongodbの依存関係を追加する。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}

接続先設定

application.ymlでDBの接続先などを定義する。

spring: 
  data:
    mongodb:
      host: localhost
      port: 27017
      database: Sample
      username: aa001
      password: aa001

エンティティの作成

  • @Documentのcollection属性に、エンティティが対応するコレクション名を指定する。
  • 引数なしコンストラクタは内部の処理で必要なので、引数ありコンストラクタを定義している場合は明示的に宣言する。
  • _idカラムに対応するフィールドには@Idを付与する。
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "Customer")
public class Customer {

    /** id */
    @Id
    public String id;

    /***/
    public String firstName;

    /***/
    public String lastName;

    /**
     * コンストラクタ。<br>
     */
    public Customer() {

    }

    /**
     * コンストラクタ。<br>
     * 
     * @param firstName
     *            名
     * @param lastName
     *            姓
     */
    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    /**
     * コンストラクタ。<br>
     * 
     * @param id
     *            ID
     * @param firstName
     *            名
     * @param lastName
     *            姓
     */
    public Customer(String id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    /**
     * 名を取得する。<br>
     * 
     * @return
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * 名を設定する。<br>
     * 
     * @param firstName
     *            名
     */
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    @Override
    public String toString() {
        return String.format(
                "Customer[id=%s, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }
}

Repositoryの作成

MongoRepositoryには基本的なCRUDメソッドが用意されているが、必要であれば独自のメソッドを定義することができる。

import java.util.List;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {

    public Customer findByFirstName(String firstName);

    public List<Customer> findByLastName(String lastName);

}

独自定義のメソッドのキーワードについて

公式ドキュメントに詳しい説明がある。
使用できるキーワードの一覧はここのページ参照。

独自定義のメソッドの引数には基本型以外にも任意の型を指定することができる。
独自定義の検索メソッドにおいて、戻り値がIterableでない際に検索結果として複数件得られた場合、IncorrectResultSizeDataAccessExceptionがスローされる。

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {
    public Customer findByCustomerSearchParam(CustomerSearchParam searchParam);
}

メソッド名に「First」キーワードがある場合は、検索結果として複数件得られた場合最初の1件が取得される。

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {
    public Customer findFirstByCustomerSearchParam(CustomerSearchParam searchParam);
}

複数カラムで検索する場合は「And」もしくは「Or」キーワードを使用する。

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {
    public Customer findByLastNameAndAge(String lastName, int age);
}

独自定義のメソッドにページャーを適用する場合は、メソッドの引数にorg.springframework.data.domain.Pageableを追加する。

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {
    public List<Customer> findByLastName(String lastName, Pageable pageable);
}

独自定義のメソッドで検索結果のソートを行いたい場合は、メソッドの引数にorg.springframework.data.domain.Sortを追加する

@Repository
public interface CustomerRepository extends MongoRepository<Customer, String> {
    public List<Customer> findByCustomerSearchParam(CustomerSearchParam searchParam, Sort sort);
}
キーワード メソッド名サンプル 条件 備考
After findByBirthdateAfter(Date date) (日時が)引数の値より後
Before findByBirthdateBefore(Date date) (日時が)引数の値より前
GreaterThan findByAgeGreaterThan(int age) (数値が)引数の値より大きい
GreaterThanEqual findByAgeGreaterThanEqual(int age) (数値が)引数の値以上
LessThan findByAgeLessThan(int age) (数値が)引数の値より小さい
LessThanEqual findByAgeLessThanEqual(int age) (数値が)引数の値以下
Between findByAgeBetween(int from, int to)
findByAgeBetween(Range range)
範囲指定 引数をintで指定した場合は境界値を含まない範囲指定となる。境界値を含む範囲指定で検索を行いたい場合はRangeで指定する
In findByAgeIn(Collection ages) カラムの値が指定した配列のいずれかの値と等しい
NotIn findByAgeNotIn(Collection ages) カラムの値が指定した配列に存在しないか、カラムが存在しない
IsNotNull
NotNull
findByFirstnameNotNull() カラムの値がnullでない
IsNull
Null
findByFirstnameNull() カラムの値がnull
IsTrue
True
findByActiveIsTrue() カラムの値がtrue
IsFalse
False
findByActiveIsFalse() カラムの値はfalse
Exists findByLocationExists(boolean exists) true:カラムの値が存在するデータの検索
false:カラムの値が存在しないデータの検索
trueで検索されるデータには、カラムの値がnullのものも含まれる
IgnoreCase findByUsernameIgnoreCase(String username) 正規表現での検索で大文字小文字を無視して検索
キーワードなし findByFirstname(String name) 完全一致検索
Not findByFirstnameNot(String name) not equal検索
Regex findByFirstnameRegex(String firstname) 正規表現を使用して検索
Like
StartingWith
EndingWith
findByFirstnameLike(String name) LIKE検索 引数には正規表現を指定
NotLike
IsNotLike
findByFirstnameNotLike(String name) NOT LIKE検索 引数には正規表現を指定
Containing findByFirstnameContaining(String name) カラムの値に引数の値が含まれる 引数には正規表現を指定
NotContaining findByFirstnameNotContaining(String name) カラムの値に引数の値が含まれない 引数には正規表現を指定
Near findByLocationNear(Point point)
findByLocationNear(Point point, Distance max)
findByLocationNear(Point point, Distance min, Distance max)
位置情報検索
Within findByLocationWithin(Circle circle)
findByLocationWithin(Box box)
位置情報検索 引数で指定された形状内に完全に存在する地理空間データの検索

○範囲指定で使用する Rangeクラス
org.springframework.data.domain.Range

// closedメソッドで定義した範囲では境界値(1と5)も範囲に含まれる
Range<Integer> closed = Range.closed(1, 5);
System.out.println(closed.contains(5)); // true

// openメソッドで定義した範囲では境界値(1と5)は範囲に含まれない
Range<Integer> open = Range.open(1, 5);
System.out.println(open.contains(5)); // false

// leftOpenメソッドで定義した範囲では境界値(1)は範囲に含まれないが境界値(5)は範囲に含まれる
Range<Integer> leftOpen = Range.leftOpen(1, 5);
// rightOpenメソッドで定義した範囲では境界値(1)は範囲に含まれるが境界値(5)は範囲に含まれない
Range<Integer> rightOpen = Range.rightOpen(1, 5);

○位置情報検索で使用するクラス
org.springframework.data.geo.Point

// 第一引数に経度、第二引数に緯度を指定
Point point = new Point(double x, double y);

org.springframework.data.geo.Distance

// 中心点からの距離を指定
Distance distance = new  Distance(double value);

org.springframework.data.geo.Circle

// 第一、第二引数で円の中心の座標、第三引数に円の半径を指定
new Circle(double centerX, double centerY, double radius);

Serviceの作成

作成したリポジトリを呼び出すServiceを作成する。

import org.springframework.stereotype.Service;

import jp.co.cti.template.webapp.mongo.Customer;
import jp.co.cti.template.webapp.mongo.CustomerRepository;
import lombok.extern.slf4j.Slf4j;

/**
 * {@link SampleService}実装クラス。<br>
 */
@Service
@Slf4j
public class SampleServiceImpl implements SampleService {

    /** */
    private CustomerRepository repository;

    /**
     * @param repository
     */
    public CalculationServiceImpl(CustomerRepository repository) {
        this.repository = repository;
    }

    @Override
    public void insertToMongoDB() {
        repository.save(new Customer("Alice", "Smith"));
    }
}

以下にデフォルトで用意されているメソッドの使用サンプルを示す。

登録

データの登録には、insertもしくはsaveメソッドを使用する。
saveメソッドは、引数に指定したエンティティのIDのデータが既に存在した場合にはupdateを行うメソッドである。
メソッドの戻り値:登録/更新されたエンティティ

public void insert() {
    // エンティティでIDに値を設定しなかった場合は、「_id」にObjectIdが自動でふられる
    repository.save(new Customer("Alice", "Smith"));
    repository.insert(new Customer("Alice", "Smith"));

    // エンティティでIDに値を設定した場合は、「_id」に指定したIDを登録できる
    repository.save(new Customer("001", "Alice", "Smith"));
    
      // 複数件の登録も可能
    List<Customer> list = List.of(new Customer("005", "中電", "三郎"), new Customer("006", "中電", "四郎"));
    repository.saveAll(list);
    repository.insert(list);
}

更新

データの更新には、saveメソッドを使用する。
メソッドの戻り値:登録/更新されたエンティティ

public void update() {
    // 引数に指定したエンティティのIDと合致するデータが存在する場合更新
    // ID未設定もしくは合致するIDが存在しない場合は登録
    repository.save(new Customer("001", "Alice", "Smith"));
    
      // 複数件の更新も可能
    List<Customer> list = List.of(new Customer("005", "中電", "三郎"), new Customer("006", "中電", "四郎"));
    repository.saveAll(list);
}

削除

データの削除にはdeleteメソッドを使用する。
メソッドの戻り値:なし

public void delete() {
    // エンティティを指定して1件削除
    repository.delete(new Customer("003", "中電", "太郎"));
    // IDを指定して削除
    repository.deleteById("001");
    // 複数件の削除(エンティティの指定)
    repository.deleteAll(List.of(new Customer("005", "中電", "三郎"), new Customer("006", "中電", "四郎")));
    // 複数件の削除(IDの指定)
    repository.deleteAllById(List.of("003", "004"));
    // 全件削除
    repository.deleteAll();
}

検索

データの検索にはfindメソッドを使用する。

public void select() {
    // 全件検索
    List<Customer> result = repository.findAll();
    // ID検索
    Optional<Customer> result = repository.findById("003");
    // 複数ID検索
    Iterable<Customer> result = repository.findAllById(List.of("003", "004"));

    // 検索結果のソート順を指定
    // Sort#byメソッドの第1引数にソート順、第2引数にソートの基準となるカラムを指定する
    List<Customer> list = repository.findAll(Sort.by(Sort.Direction.ASC, "id"));

    // ページャー指定
    // PageRequest#ofメソッドの第1引数に取得したいページ番号(0始まり)、
    // 第2引数に1ページ当たりのデータ数、第3引数にSortのインスタンスを指定する
    PageRequest pageRequest = PageRequest.of(3, 3, Sort.by(Sort.Direction.ASC, "id"));
    Page<Customer> findAll2 = repository.findAll(pageRequest);

    Customer customer = new Customer();
    customer.setFirstName("中電");

    ExampleMatcher matcher = ExampleMatcher.matching();
    Example<Customer> example = Example.of(customer, matcher);

    // Exampleで指定した条件に合うデータの検索
    // 上記のExampleの場合「firstName=中電」のデータが検索される
    List<Customer> findAll = repository.findAll(example);

    // Exampleで指定した条件に合うデータのうち1件を取得
    Optional<Customer> findOne = repository.findOne(example);
}

データ存在確認

データの存在確認はexistsメソッドを使用する。

public void exists() {
    // IDに合致するデータが存在する場合true, 存在しない場合false
    boolean existsById = repository.existsById("001");
    // Exampleで指定した条件に合うデータが存在する場合true, 存在しない場合false
    boolean exists = repository.exists(example);
}

データ数カウント

データ数カウントはcountメソッドを使用する。

public void count() {
    // コレクションに存在するデータ数を返す
    long count = repository.count();
    // Exampleで指定した条件に合うデータ数を返す
    long count2 = repository.count(example);
}