JPAに慣れていると、MongoDBを使おうとしたときに「何から始めればいいの?」と戸惑いますよね。アノテーションもリポジトリも見た目は似ているけど、概念が微妙に違う。
この記事では Spring Boot 3.x(Java 17以上) を対象に、MongoDBを接続して基本的なCRUD・クエリ・集計まで動かせるようになることを目標にします。
MongoDBとRDBMSの概念対比
まず頭を切り替えましょう。MongoDBはテーブルではなく Collection にデータを格納し、行の代わりに Document (JSON形式)を扱います。
| RDBMS | MongoDB |
|---|---|
| Table | Collection |
| Row | Document |
| Primary Key | _id |
| Schema | スキーマレス(柔軟) |
スキーマレスというのは、同じコレクション内のドキュメントがバラバラなフィールドを持てるということです。スキーマが頻繁に変わるデータや、深いネスト構造を持つデータに向いています。逆に、複雑なトランザクションや厳格な整合性が必要な場面はRDBMSの方が安心です。
ローカル環境をDocker Composeで準備する
MongoDBをローカルにインストールしなくても、Docker Composeで簡単に起動できます。
# docker-compose.yml
services:
mongo:
image: mongo:latest # ローカル開発用。本番では特定バージョン(例: mongo:7.0)を指定すること
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
docker compose up -d で起動するだけです。認証なしのシンプルな設定ですが、ローカル開発では十分です。Dockerを使った環境構築については Spring BootをDockerでコンテナ化する方法 も参考にしてください。
依存関係の追加と接続設定
build.gradle に以下を追加します。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}
application.properties の接続設定はこれだけです。
spring.data.mongodb.uri=mongodb://localhost:27017/mydb
Spring Bootの自動設定が動いて、MongoClient の生成からコネクションプールの設定まで全部やってくれます。JPAのときと同じ感覚ですね。
@DocumentでエンティティクラスとMongoDBを紐付ける
@Entity の代わりに @Document を使います。
@Document(collection = "products")
public class Product {
@Id
private String id; // ObjectIdはString型で受け取れる
private String name;
private int price;
@Field("category_name")
private String categoryName; // DBのフィールド名をカスタマイズ
// コンストラクタ・getters・setters 省略
}
ネストしたオブジェクトは別のクラスをそのままフィールドに持てば、埋め込みドキュメントとして自動的に扱われます。@Embedded のようなアノテーションは不要です。
MongoRepositoryでCRUD操作を実装する
JpaRepository と同じように、MongoRepository を継承するだけで基本的なCRUDが使えます。
public interface ProductRepository extends MongoRepository<Product, String> {
// save / findById / findAll / deleteByIdはそのまま使える
}
サービス層での使い方もJPAとほぼ同じです。
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product create(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public void delete(String id) {
productRepository.deleteById(id);
}
}
REST APIとの組み合わせ方は Spring BootでREST APIを作るチュートリアル が参考になります。
クエリメソッドで条件検索を書く
Spring Data JPAのクエリメソッドと 全く同じ命名規則 が使えます。
public interface ProductRepository extends MongoRepository<Product, String> {
List<Product> findByName(String name);
List<Product> findByPriceLessThan(int price);
List<Product> findByNameAndCategoryName(String name, String categoryName);
@Query("{ 'price': { $gte: ?0, $lte: ?1 } }")
List<Product> findByPriceRange(int min, int max);
}
JPAのクエリメソッド命名規則については Spring Data JPAのクエリメソッド解説 も合わせて読んでみてください。
MongoTemplateで動的クエリを書く
クエリメソッドは静的な条件には便利ですが、「条件がある場合だけフィルタする」といった動的クエリは MongoTemplate の方が書きやすいです。
@Service
public class ProductSearchService {
private final MongoTemplate mongoTemplate;
public ProductSearchService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
public List<Product> searchProducts(String name, Integer maxPrice) {
Query query = new Query();
if (name != null) {
query.addCriteria(Criteria.where("name").regex(name, "i"));
}
if (maxPrice != null) {
query.addCriteria(Criteria.where("price").lte(maxPrice));
}
return mongoTemplate.find(query, Product.class);
}
}
Criteria.where("field").is(value) で条件を組み立て、Query オブジェクトに追加していくスタイルです。なお addCriteria() は 異なるフィールドに対して 呼ぶ必要があります。同じフィールドに2回呼ぶと InvalidMongoDbApiUsageException が発生するので注意してください。
Aggregation Pipelineで集計する
MongoDBのAggregation Pipelineは、Spring Data APIでも表現できます。まず集計結果を受け取るクラスを定義します。
public class CategorySummary {
@Id
private String id; // $groupのキー(categoryName)が入る
private int productCount;
private long totalPrice;
// getters省略
}
次に、パイプラインを組み立てて実行します。
public List<CategorySummary> aggregateByCategory() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("price").gt(0)), // $match
Aggregation.group("categoryName")
.count().as("productCount")
.sum("price").as("totalPrice"), // $group
Aggregation.sort(Sort.Direction.DESC, "productCount") // $sort
);
AggregationResults<CategorySummary> results =
mongoTemplate.aggregate(aggregation, "products", CategorySummary.class);
return results.getMappedResults();
}
$match で絞り込み、$group で集計、$sort で並び替えというMongoDBのパイプライン構造がそのままJavaコードで表現できます。AggregationResults#getMappedResults() で CategorySummary のリストとして受け取れます。
RDBMSとMongoDBの使い分け
どちらを選ぶかは、データの性質と要件次第です。
MongoDBが向く場面
- スキーマが頻繁に変化するデータ(設定情報、ユーザー属性など)
- ネスト構造が自然なデータ(注文+明細、記事+コメントなど)
- 水平スケーリングが前提の大規模データ
RDBMSが向く場面
- 複雑なトランザクションや厳格な整合性が必要
- 正規化されたデータで複雑なJOINが多い
- 既存のJPAベースのコードが大量にある
一つのシステムでRDBMSとMongoDBを用途に応じて使い分ける「ポリグロット構成」も現実的な選択肢です。RDB中心のアーキテクチャとの比較は Spring BootのMyBatis vs JPA比較記事 も参考にしてみてください。
まとめ
Spring Data MongoDBはJPAと似た使い心地になるよう設計されているので、JPA経験者であれば比較的すんなり入れます。まとめると以下の5点を押さえるだけで、Spring BootアプリにMongoDBを組み込めます。
@Documentと@Idでエンティティ定義MongoRepositoryで基本CRUD- クエリメソッドはJPAと同じ命名規則
- 動的クエリは
MongoTemplateとCriteria - 集計は
Aggregation.newAggregation()
テストについては TestcontainersでSpring Bootの統合テストを書く方法 でMongoDBコンテナを使った統合テストも書けるので、合わせて確認してみてください。