# 12.3.2 MongoDB 持久化实体映射

Spring Data MongoDB 提供了一组注解，用于将实体类映射到 MongoDB 的文档结构上。这组 Spring Data MongoDB 注解有好几个，其中有三个是最常用的：

* `@Id` —— 将属性指定为文档的 Id（在 Spring Data Commons 中）
* `@Document` —— 声明实体类型要持久化为 MongoDB 文档
* `@Field` —— 指定持久化文档中存储这个属性的字段名称（以及可选的顺序）

在这三个注释中，只有 `@Id` 和 `@Document` 注解是必须有的。除非另行指定，否则没有使用 `@Field` 注解的属性，将假定字段名等于属性名。

为 Ingredient 类添加这些注解后，可以得到以下结果：

```java
package tacos;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Document
public class Ingredient {

    @Id
    private final String id;
    private final String name;
    private final Type type;

    public static enum Type {
        WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }
}
```

如您所见，您将 `@Document` 注解放置在类级别上，以指示 Ingredient 是一个文档实体，可以写入 Mongo 数据库并从中读取。默认情况下，`Collection` 名称（Mongo 中类似于关系数据库的 Table）基于类名，第一个字母小写。因为你没有进行指定，Ingredient 对象将持久化到名为 `ingredient` 的 `Collection` 中。但是您可以通过设置 @Document 的 `collection` 属性来改变这一点：

```java
@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Document(collection="ingredients")
public class Ingredient {
    ...
}
```

您还注意到 id 属性已经添加了 `@id` 注解，这指定了此属性作为持久化文档的 ID 。您可以在任何属性上使用 `@Id` 注解，只要其类型是可序列化的，这包括 String 和 Long。在现在这种情况下，您的 id 属性是 String 类型，所以没有必要将其更改为其他类型。

到目前为止，进展还不错。您可能还记得在本章前面的内容中，为 Cassandra 添加映射时，Ingredient 类是容易处理的实体类型。其他类型，如 Taco，更复杂一些。让我们看看现在如何映射 Taco 类。

与前面一样，您肯定需要为 Taco 类添加 `@Document` 注解。您还需要用 @Id 指定一个 ID 属性。这样做以后的 Taco 类如下：

```java
@Data
@RestResource(rel="tacos", path="tacos")
@Document
public class Taco {

    @Id
    private String id;

    @NotNull
    @Size(min=5, message="Name must be at least 5 characters long")
    private String name;

    private Date createdAt = new Date();

    @Size(min=1, message="You must choose at least 1 ingredient")
    private List<Ingredient> ingredients;
}
```

信不信由你，这样就可以了！处理两个不同主键，以及用户自定义类型，只是特定于 Cassandra 的。对于 MongoDB 来说，Taco 类的映射要简单得多。

即便如此，在 Taco 中还是有一些有趣的事情需要指出来。首先，注意 id 属性已更改为 String（而不是 JPA 版本中的 Long，或者 Cassandra 版的 UUID）。正如我之前所说，`@Id` 可以应用于任何可序列化的类型。但是如果您选择使用 String 属性作为 ID，那么 Mongo 会在保存时自动为其赋值（如果它是空的）。通过选择 String，您可以获得一个由数据库管理的自动分配的 ID，而不必费心手动设置该属性。

另外，注意 ingredients 属性，这是一个列表，就像第三章的 JPA 版本一样。但与 JPA 版本不同的是：不是存储在单独的 MongoDB 集合中。就像 Cassandra 时那样，taco 表直接存储在 taco 文档中，这是非规范化的。但是与 Cassandra 的实现不同，您不需要构建用户自定义的类型，在这里 MongoDB 允许使用任何类型。不管它是另一个添加了 `@Document` 注解的类型或只是一个 POJO。

看到将 Taco 映射为文档结构进行 Mongo 持久性很容易，这无疑是一种解脱。这种映射的简单性会延续到 Order 类吗？您自己看看添加了 MongoDB 注解的 Order 类吧：

```java
@Data
@Document
public class Order implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    private String id;

    private Date placedAt = new Date();

    @Field("customer")
    private User user;

    // other properties omitted for brevity's sake

    private List<Taco> tacos = new ArrayList<>();

    public void addDesign(Taco design) {
        this.tacos.add(design);
    }
}
```

为了简洁起见，我删掉了 delivery 和 credit card 字段。但是从剩下的属性来看，很明显，您只需要 `@Document` 和 `@Id` 注解，就像其他实体类型那样。即便如此，您还是要为 user 属性加上 `@Field` 注解，以指定在持久化文档中，使用 `customer` 字段来进行存储。

从到目前为止的经验可以得知，映射 MongoDB 的 User 类应该同样容易：

```java
@Data
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@RequiredArgsConstructor
@Document
public class User implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    private String id;

    private final String username;

    private final String password;
    private final String fullname;
    private final String street;
    private final String city;
    private final String state;
    private final String zip;
    private final String phoneNumber;

    // UserDetails method omitted for brevity's sake
}
```

尽管有一些更高级和不寻常的情况，需要额外的映射配置。但在大多数情况下，`@Document` 和 `@Id` 以及偶尔使用 `@Field`，对于 MongoDB 的映射就足够了。这三个注解就完全满足了 Taco Cloud 应用的类型映射工作。

剩下的就是编写 Repository 接口了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v5/di-12-zhang-xiang-ying-shi-chi-jiu-hua-shu-ju/12.3-bian-xie-xiang-ying-shi-mongodb-ku/12.3.2-mongodb-chi-jiu-hua-shi-ti-ying-she.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
