12.3.2 MongoDB 持久化实体映射

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

  • @Id —— 将属性指定为文档的 Id(在 Spring Data Commons 中)

  • @Document —— 声明实体类型要持久化为 MongoDB 文档

  • @Field —— 指定持久化文档中存储这个属性的字段名称(以及可选的顺序)

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

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

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 对象将持久化到名为 ingredientCollection 中。但是您可以通过设置 @Document 的 collection 属性来改变这一点:

@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 类如下:

@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 类吧:

@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 类应该同样容易:

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

最后更新于