Spring Data MongoDB简介

2023/05/18

1. 概述

本文将快速实用地介绍Spring Data MongoDB

我们将使用MongoTemplate和MongoRepository来介绍基础知识,并通过实际示例来说明每个操作。

延伸阅读

MongoDB中的地理空间支持

了解如何使用MongoDB存储、索引和搜索地理空间数据

阅读更多

使用嵌入式MongoDB进行Spring Boot集成测试

了解如何结合使用Flapdoodle的嵌入式MongoDB解决方案和Spring Boot来顺利运行MongoDB集成测试。

阅读更多

2. MongoTemplate和MongoRepository

MongoTemplate遵循Spring中的标准模板模式,并为底层持久化引擎提供了一个随时可用的基本API。

Repository遵循以Spring Data为中心的方法,并基于所有Spring Data项目中众所周知的访问模式,提供更灵活和复杂的API操作。

对于两者,我们需要从定义依赖关系开始-例如,在pom.xml中,使用Maven:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>3.0.3.RELEASE</version>
</dependency>

要检查是否已发布任何新版本的库,请在此处跟踪版本。

3. MongoTemplate的配置

3.1 XML配置

让我们从Mongo模板的简单XML配置开始:

<mongo:mongo-client id="mongoClient" host="localhost" />
<mongo:db-factory id="mongoDbFactory" dbname="test" mongo-client-ref="mongoClient" />

我们首先需要定义负责创建Mongo实例的工厂bean。

接下来,我们需要实际定义(和配置)模板bean:

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> 
    <constructor-arg ref="mongoDbFactory"/> 
</bean>

最后,我们需要定义一个后处理器来转换@Repository注解类中抛出的任何MongoException:

<bean class= "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

3.2 Java配置

现在让我们通过扩展MongoDB配置AbstractMongoConfiguration的基类,使用Java配置创建一个类似的配置:

@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("cn.tuyucheng.taketoday");
    }
}

请注意,我们不需要在之前的配置中定义MongoTemplate bean,因为它已经在AbstractMongoClientConfiguration中定义了。

我们还可以在不扩展AbstractMongoClientConfiguration的情况下从头开始使用我们的配置:

@Configuration
public class SimpleMongoConfig {

    @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");
    }
}

4. MongoRepository的配置

4.1 XML配置

要使用自定义Repository(扩展MongoRepository),我们需要继续第3.1节中的配置。并设置Repository:

<mongo:repositories base-package="cn.tuyucheng.taketoday.repository" mongo-template-ref="mongoTemplate"/>

4.2 Java配置

同样,我们将在第3.2节中创建的配置的基础上进行构建。并添加一个新注解:

@EnableMongoRepositories(basePackages = "cn.tuyucheng.taketoday.repository")

4.3 创建Repository

配置完成后,我们需要创建一个Repository-扩展现有的MongoRepository接口:

public interface UserRepository extends MongoRepository<User, String> {
    // 
}

现在我们可以自动注入这个UserRepository并使用来自MongoRepository的操作或添加自定义操作。

5. 使用MongoTemplate

5.1 Insert

让我们从插入操作和一个空数据库开始:

{
}

现在,如果我们插入一个新用户:

User user = new User();
user.setName("Jon");
mongoTemplate.insert(user, "user");

数据库将如下所示:

{
    "_id" : ObjectId("55b4fda5830b550a8c2ca25a"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jon"
}

5.2 Save-Insert

保存操作具有保存或更新语义:如果存在id,则执行更新,如果不存在,则执行插入。

让我们看看第一个语义-插入。

这是数据库的初始状态:

{
}

当我们现在保存一个新用户时:

User user = new User();
user.setName("Albert"); 
mongoTemplate.save(user, "user");

该实体将被插入到数据库中:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Albert"
}

接下来,我们将查看具有更新语义的相同操作-保存。

5.3 Save-Update

现在让我们看一下使用更新语义保存,对现有实体进行操作:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jack"
}

当我们保存现有用户时,我们将更新它:

user = mongoTemplate.findOne(Query.query(Criteria.where("name").is("Jack")), User.class);
user.setName("Jim");
mongoTemplate.save(user, "user");

数据库将如下所示:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jim"
}

我们可以看到,在这个特定的例子中,save使用了update的语义,因为我们使用了一个给定_id的对象。

5.4 UpdateFirst

updateFirst更新匹配查询的第一个文档。

让我们从数据库的初始状态开始:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Alex"
    },
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614c"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Alex"
    }
]

当我们现在运行updateFirst时:

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Alex"));
Update update = new Update();
update.set("name", "James");
mongoTemplate.updateFirst(query, update, User.class);

只有第一个条目将被更新:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "James"
    },
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614c"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Alex"
    }
]

5.5 UpdateMulti

UpdateMulti更新与给定查询匹配的所有文档

首先,这是执行updateMulti之前数据库的状态:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eugen"
    },
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614c"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Eugen"
    }
]

现在让我们运行updateMulti操作:

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Eugen"));
Update update = new Update();
update.set("name", "Victor");
mongoTemplate.updateMulti(query, update, User.class);

两个现有对象都将在数据库中更新:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Victor"
    },
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614c"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Victor"
    }
]

5.6 FindAndModify

此操作与updateMulti类似,但它返回修改前的对象

首先,这是调用findAndModify之前数据库的状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Markus"
}

下面来看实际运行代码:

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Markus"));
Update update = new Update();
update.set("name", "Nick");
User user = mongoTemplate.findAndModify(query, update, User.class);

返回的用户对象与数据库中的初始状态具有相同的值。

但是,这是数据库中的新状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Nick"
}

5.7 Upsert

upsert具有查找并修改或创建语义:如果文档匹配,则更新它,或者通过组合查询和更新对象来创建新文档。

让我们从数据库的初始状态开始:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Markus"
}

现在让我们运行upsert:

Query query = new Query();
query.addCriteria(Criteria.where("name").is("Markus"));
Update update = new Update();
update.set("name", "Nick");
mongoTemplate.upsert(query, update, User.class);

这是操作后数据库的状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Nick"
}

5.8 Remove

我们将在调用remove之前查看数据库的状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Benn"
}

现在让我们运行remove:

mongoTemplate.remove(user, "user");

结果将如预期的那样:

{
}

6. 使用MongoRepository

6.1 Insert

首先,我们将在运行插入之前查看数据库的状态:

{
}

现在我们将插入一个新用户:

User user = new User();
user.setName("Jon");
userRepository.insert(user);

这是数据库的最终状态:

{
    "_id" : ObjectId("55b4fda5830b550a8c2ca25a"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jon"
}

请注意该操作的工作方式与MongoTemplate API中的插入相同。

6.2 Save–Insert

同样,save与MongoTemplate API中的save操作相同。

这是数据库的初始状态:

{
}

现在我们执行save操作:

User user = new User();
user.setName("Aaron");
userRepository.save(user);

结果是用户被添加到数据库中:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Aaron"
}

再次注意save如何与insert语义一起工作,因为我们正在插入一个新对象。

6.3 Save–Update

现在让我们看一下相同的操作,但具有更新语义

首先,这是运行新保存之前的数据库状态:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jack"81*6
}

现在我们执行操作:

user = mongoTemplate.findOne(Query.query(Criteria.where("name").is("Jack")), User.class);
user.setName("Jim");
userRepository.save(user);

最后,这是数据库的状态:

{
    "_id" : ObjectId("55b52bb7830b8c9b544b6ad5"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Jim"
}

再次注意保存如何与更新语义一起工作,因为我们使用的是现有对象。

6.4 Delete

这是调用delete之前数据库的状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Benn"
}

让我们运行delete:

userRepository.delete(user);

这是我们的结果:

{
}

6.5 FindOne

接下来,这是调用findOne时数据库的状态:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Chris"
}

现在让我们执行findOne:

userRepository.findOne(user.getId())

结果将返回现有数据:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Chris"
}

6.6 Exists

调用之前数据库的状态存在:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Harris"
}

现在让我们运行exists,它当然会返回true:

boolean isExists = userRepository.exists(user.getId());

6.7 FindAll与Sort

调用findAll之前的数据库状态:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Brendan"
    },
    {
       "_id" : ObjectId("67b5ffa5511fee0e45ed614b"),
       "_class" : "cn.tuyucheng.taketoday.model.User",
       "name" : "Adam"
    }
]

现在让我们使用Sort运行findAll:

List<User> users = userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

结果将按名称升序排序

[
    {
        "_id" : ObjectId("67b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Adam"
    },
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Brendan"
    }
]

6.8 FindAll与Pageable

调用findAll之前的数据库状态:

[
    {
        "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Brendan"
    },
    {
        "_id" : ObjectId("67b5ffa5511fee0e45ed614b"),
        "_class" : "cn.tuyucheng.taketoday.model.User",
        "name" : "Adam"
    }
]

现在让我们使用Pageable执行findAll:

Pageable pageableRequest = PageRequest.of(0, 1);
Page<User> page = userRepository.findAll(pageableRequest);
List<User> users = pages.getContent();

生成的用户列表将只有一个用户:

{
    "_id" : ObjectId("55b5ffa5511fee0e45ed614b"),
    "_class" : "cn.tuyucheng.taketoday.model.User",
    "name" : "Brendan"
}

7. 注解

最后,让我们回顾一下Spring Data用来驱动这些API操作的简单注解。

字段级@Id注解可以修饰任何类型,包括long和String:

@Id
private String id;

如果@Id字段的值不为null,则按原样存储在数据库中;否则,转换器将假定我们要在数据库中存储一个ObjectId(可以是ObjectId、String或BigInteger)。

接下来我们将看看@Document:

@Document
public class User {
    //
}

这个注解只是将一个类标记为需要持久保存到数据库的域对象,同时允许我们选择要使用的集合的名称。

8. 总结

本文快速而全面地介绍了如何通过MongoTemplate API以及如何使用MongoRepository将MongoDB与Spring Data结合使用。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章