> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](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).

# 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 接口了。
