首页 > 编程笔记

Spring Boot集成MongoDB

除了传统的关系型数据库之外,在当前流行的应用程序开发中,非关系型数据库也占有不小的比重。在众多非关系型数据库中,MongoDB 是最为流行的非关系型数据库之一。MongoDB 定义为文档型数据库,它具有高性能、易部署并且易使用的特点。本文将介绍如何借助 Spring Data MongoDB 集成 MongoDB。

准备工作

Spring Data MongoDB 的配置非常简单,依赖方面引入 Spring Data 提供的 Starter 即可:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Spring Data MongoDB 提供的数据访问大致基于 MongoTemplate 与 MongoRepository 这两种方式。

  1. MongoTemplate 遵循 Spring Boot 的标注模板形式,是在官方客户端的基础之上封装的持久化引擎。
  2. MongoRepository 则是按照 Spring Data 这个“大家族”中通用的设计模式所设计的 API。

Spring Data MongoDB 可以通过创建配置类继承 AbstractMongoClientConfiguration 进行配置,或者通过声明 MongoClient 与 MongoTemplate 的JavaBean 实现。

示例

继承 AbstractMongoClientConfiguration 的示例代码:
@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Override
    protected String getDatabaseName() {
        return "test";
    }

    @Override
    public MongoClient mongoClient() {
        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionstring).build();
        return MongoClients.create(mongoClientSettings);
    }

    @Override
    public Collection getMappingBasePackages() {
        return Collections.singleton("com.example");
    }
}
返回 JavaBean 的示例代码:
@Configuration
public class MongoConfig {

    @Bean
    public MongoClient mongo() {
        Connectionstring connectionstring = new Connectionstring("mongodb://localhost:27017/test");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionstring).build();
        return MongoClients.create(mongoClientSettings);
    }

    @Bean
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), "test");
    }
}
如果需要启用 Repository 对数据库进行操作,则需要加上注解 @EnableMongoRepositories(basePackages = "com.example.mongodb.repository")。另外还需要创建对应的 Repository 接口,示例代码如下:
public interface WareRepository extends MongoRepository<Ware, String> {
    //Ware为实体类型,String为实体的主键类型
}

使用MongoTemplate访问MongDB

Spring Data Mongodb 中基本文档查询依赖于 MongoTemplate。下面我们介绍基于 MongoTemplate 基础的增删改查。

1) <T> T insert(T objectToSave, String collectionName);

该方法用于在初始化时将数据写入到数据库当中。例如数据库内容为空。执行代码:
Ware mineralWater = new Ware();
mineralWater.setName("矿泉水");
mongoTemplate.insert(mineralWater, "ware");
执行结果:

{
    "_id" : ObjectId("5f5c76db61083f0fb8ce7581"),
    "name" : "矿泉水",
    "_class" : "com.example.mongodb.domain.Ware"
}

2) <T> T save(T objectToSave);

该方法用于保存一个实体。当实体不存在数据库中时,保存操作会被视为“插入”。当实体已存在于数据库中(通过 Id 判定实体是否存在于数据库中),保存操作将被视为“更新”。例如数据库中已存在一个 Id 为 1 的实体记录:

{
    "_id" : "1",
1.
     "_class" : "com.example.mongodb.domain.Ware"
}

执行代码:
Ware melonSeeds=new Ware();
melonSeeds.setld("1").setName("瓜子");
mongoTemplate.save(melonSeeds);
执行结果:

{
    "_id" : "1",
    "name" : "瓜子",
    "_class" : "com.example.mongodb.domain.Ware"
}

3) UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?>entityClass);

updateFirst 方法用于更新通过 Query 查询到的第一条实体记录。例如数据库中存在名称相同的多条记录:

[
    {
        "_id":Objectld("5f5c706f81ffe73b0b5762e4"),
        "name":"Milk",
        " _class":"com.example.mongodb.domain.Ware"
    },{
        "_id" :Objectld ("5f5c70dd8da6441372dfc517"),
        "name":"Milk",
        "_class":"com.example.mongodb.domain.Ware"
    }
]

执行代码:
Query query = new Query();
query.addCriteria(Criteria.where("name").is("Milk"));
Update update = new Update();
update.set("name", "Yogurt");
mongoTemplate.updateFirst(query, update, Ware.class);
执行结果:

[
    {
        "_id":ObjectId("5f5c706f81ffe73b0b5762e4"),
        "name": "Yogurt",
        "_class":"com.example.mongodb.domain.Ware"
    },{
        "_id":ObjectId("5f5c70dd8da6441372dfc517"),
        "name":"Milk",
        "_class":"com.example.mongodb.domain.Ware"
    }
]

4) UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?>entityClass);

updateMulti 方法根据 Query 对象查询到多条数据,对该批数据进行批量更新。例如数据库中有多条 name 相同的实体对象:

[
    {
        "_id":ObjectId("5f5c7d7993b72416be19c44e"),
        "name" : "矿泉水",
        "status":"selling",
        " _class":"com.example.mongodb.domain.Ware"
    },{
        "_id":ObjectId(H5f5c7d7eda0db17b306fad4c"),
        "name" : "矿泉水",
        "status":"selling",
        "_class":"com.example.mongodb.domain.Ware"
    }
]

执行代码:
Query query = new Query();
query.addCriteria(Criteria.where("name").is("矿泉水"));
Update update = new Update();
update.set("status", "sold out");
mongoTemplate.updateMulti(query, update, Ware.class);
执行结果:

[
    {
        "_id":Objectld("5f5c7d7993b72416be19c44e"),
        "name":"矿泉水",
        "status":"sold out",
        "_class":"com.example.mongodb.domain.Ware"
    },{
        "_id":Objectld("5f5c7d7eda0db17b306fad4c"),
        "name":"矿泉水",
        "status":"sold out",
        "_class":"com.example.mongodb.domain.Ware"
    }
]

5) <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass);

findAndModify 方法根据 Query 查询对应实体对其进行更新,类似于 updateFirst。只不过方法的返回值为更新前的记录,例如数据库中有实体如下:

{
    "_id": ObjectId("5f5c76db61083f0fb8ce7581"),
    "name": "矿泉水",
    "_class" : "com.example.mongodb.domain.Ware",
    "status" : "sold out"
}

执行代码:
Query query = new Query();
query.addCriteria(Criteria.where("name").is("矿泉水"));
Update update = new Update();
update.set("status", "selling");
Ware result = mongoTemplate.findAndModify(query, update, Ware.class);
assert Optional.ofNullable(result).orElse(newWare()).getStatus().equals("sold out");
执行结果:

{
    "_id" : ObjectId("5f5c76db61083f0fb8ce7581"),
    "name" : "矿泉水",
    "_class" : "com. example .mongodb. domain. Ware",
    "status" : "selling"
}

6) UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass);

upsert 方法用于查找并更新或者创建实体。类似于 save 方法,当根据 Query 查找实体失败时,则创建对应实体。区别在于该方法根据 Query 判断实体是否存在,而 save 根据 Id 进行判断。例如数据库中无内容。

执行代码:
Query query = new Query();
query.addCriteria(Criteria.where("name").is("西瓜"));
Update update = new Update();
update.set("name", "哈密瓜");
mongoTemplate.upsert(query, update, Ware.class);
执行结果:

{
    "_id" : ObjectId("5f5caf71d28dbdbeb1aa261a"),
    "name" : "哈密瓜"
}

如果数据库中内容为:

{
    "_id" : ObjectId("5f5cf4120fb5c25ea0de99ba"),
    "name" : "西瓜",
    "status" : "selling",
    "_class" : "com.example.mongodb.domain.Ware"
}

执行结果:

{
    "_id" : ObjectId("5f5cf4120fb5c25ea0de99ba"),
    "name" : "哈密瓜",
    "status" : "selling",
    "_class" : "com.example.mongodb.domain.Ware"
}

7) DeleteResult remove(Query query, Class<?> entityClass);

remove 方法用于根据 Query 查找对应实体对象,并将符合条件的对象从数据库中移除。例如数据库中 status 为 sold out 的对象如下:

[
    {
        "_id" : Objectld("5f5c7d7993b72416be19c44e"),
        "name": "矿泉水",
        "status" : "sold out",
        "_class" : "com.example.mongodb.domain.Ware"
    },{
        "_id" : Objectld("5f5c7d7eda0db17b306fad4c"),
        "name": "矿泉水",
        "status" : "sold out",
        "_class" : "com.example.mongodb.domain.Ware"
    }
]

执行代码:
mongoTemplate.remove(Query.query(Criteria.where("status").is("sold out")),Ware.class);
最终实体对象被移除。

使用MongoRepository访问MongoDB

MongoRepository 与 JPA 中的 Repository 很相似,它继承了 PagingAndSortingRepository<T, ID>、QueryByExampleExecutor<T> 这两个接口。除提供基础的增删改查功能之外还提供了诸如排序、分页之类的功能,另外提供了通过 Example 这一匹配对象的方式。
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
saveAll 方法用于保存所有给出的实体对象。示例代码如下:
List<Ware> wares = new ArrayList<>();
wares.add(new Ware().setName("酒水").setStatus("selling"));
wares.add(new Ware().setName("瓜子").setStatus("selling"));
wares.add(new Ware().setName("饮料").setStatus("selling"));
wareRepository.saveAll(wares);
<S extends T> boolean exists(Example<S> example);
根据 Example 判断是否存在与 example 匹配的元素。例如数据库中不含 status 为 sold out 的元素,仅包含 status 为 selling 的元素。示例代码如下:
boolean isSoldOutExist = wareRepository.exists(Example.of(newWare().setStatus("sold out")));
assert !isSoldOutExist;
boolean isSellingExist = wareRepository.exists(Example.of(new
Ware().setStatus("selling")));
assert isSellingExist;
Iterable<T> findAll(Sort sort);
该方法将会查出所有记录,结果按排序规则进行排序。示例代码如下:
List<Ware> wareList = wareRepository.findAll(Sort.by(Sort.Direction.ASC,"id"));
Page<T> findAll(Pageable pageable);
该方法将会查出所有记录,结果按分页规则进行分页。示例代码如下:
Page<Ware> warePage = wareRepository.findAll(PageRequest.of(0, 10));

优秀文章