Skip to content

4.2.2 Ánh xạ các kiểu miền (domain types) sang tài liệu (documents)

Spring Data MongoDB cung cấp một vài annotation hữu ích để ánh xạ các kiểu miền sang cấu trúc tài liệu sẽ được lưu trữ trong MongoDB. Mặc dù Spring Data MongoDB cung cấp khoảng nửa tá annotation cho việc ánh xạ, nhưng chỉ có bốn annotation sau đây là hữu dụng cho phần lớn các trường hợp phổ biến:

  • @Id —— Đánh dấu một thuộc tính là ID của tài liệu (từ Spring Data Commons)
  • @Document —— Khai báo một kiểu miền là tài liệu sẽ được lưu trữ trong MongoDB
  • @Field —— Chỉ định tên trường (và tùy chọn thứ tự) để lưu một thuộc tính trong tài liệu đã lưu
  • @Transient —— Chỉ định rằng một thuộc tính sẽ không được lưu trữ

Trong số các annotation đó, chỉ có @Id@Document là thực sự bắt buộc. Trừ khi bạn chỉ định khác đi, các thuộc tính không được chú thích với @Field hoặc @Transient sẽ sử dụng tên trường trùng với tên thuộc tính.

Khi áp dụng các annotation này cho lớp Ingredient, bạn sẽ có như sau:

java
package tacos;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

  @Id
  private String id;
  private String name;
  private Type type;
  public static enum Type {
    WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
  }

}

Như bạn thấy, bạn đặt annotation @Document ở cấp lớp để chỉ ra rằng Ingredient là một thực thể tài liệu có thể được ghi và đọc từ cơ sở dữ liệu Mongo. Theo mặc định, tên collection (tương tự như bảng trong cơ sở dữ liệu quan hệ) được dựa trên tên lớp, với chữ cái đầu tiên viết thường. Vì bạn chưa chỉ định gì khác, các đối tượng Ingredient sẽ được lưu trữ vào collection có tên là ingredient. Nhưng bạn có thể thay đổi điều đó bằng cách thiết lập thuộc tính collection của @Document, như sau:

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

Bạn cũng sẽ thấy rằng thuộc tính id đã được chú thích với @Id. Annotation này đánh dấu thuộc tính là ID của tài liệu được lưu trữ. Bạn có thể sử dụng @Id với bất kỳ thuộc tính nào có kiểu là Serializable, bao gồm StringLong. Trong trường hợp này, bạn đã sử dụng thuộc tính id kiểu String làm định danh tự nhiên, vì vậy không cần thay đổi nó sang kiểu khác.

Cho đến đây thì mọi thứ khá ổn. Nhưng bạn sẽ nhớ từ phần trước của chương rằng Ingredient là kiểu miền dễ ánh xạ cho Cassandra. Các kiểu miền khác, như Taco, sẽ hơi phức tạp hơn. Hãy xem bạn có thể ánh xạ lớp Taco như thế nào và xem có điều bất ngờ nào không.

Cách tiếp cận của MongoDB trong việc lưu trữ tài liệu rất phù hợp với phương pháp thiết kế theo domain-driven design, nơi persistence được áp dụng ở cấp aggregate root. Tài liệu trong MongoDB thường được định nghĩa là các aggregate root, với các thành phần của aggregate là các subdocument.

Điều đó có nghĩa là, đối với Taco Cloud, vì Taco chỉ được lưu trữ như là một phần của aggregate gốc TacoOrder, nên lớp Taco không cần được chú thích với @Document, cũng không cần thuộc tính @Id. Lớp Taco có thể giữ nguyên, không có bất kỳ annotation persistence nào, như sau:

java
package tacos;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class Taco {

  @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 = new ArrayList<>();

  public void addIngredient(Ingredient ingredient) {
    this.ingredients.add(ingredient);
  }
}

Tuy nhiên, lớp TacoOrder, với vai trò là gốc của aggregate, sẽ cần được chú thích với @Document và có một thuộc tính @Id, như sau:

java
package tacos;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.CreditCardNumber;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.Data;

@Data
@Document
public class TacoOrder implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  private String id;

  private Date placedAt = new Date();

  // other properties omitted for brevity's sake

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

  public void addTaco(Taco taco) {
    this.tacos.add(taco);
  }
}

Tuy nhiên, lớp TacoOrder, với vai trò là gốc của aggregate, sẽ cần được chú thích với @Document và có một thuộc tính @Id, như sau:

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
}

Để rút ngắn, một số trường như địa chỉ giao hàng và thẻ tín dụng đã được lược bỏ. Nhưng từ phần còn lại, có thể thấy rằng bạn chỉ cần @Document@Id, giống như với các kiểu miền khác.

Lưu ý rằng thuộc tính id đã được chuyển sang kiểu String (khác với kiểu Long trong phiên bản JPA hoặc UUID trong phiên bản Cassandra). Như đã nói ở trên, @Id có thể được áp dụng cho bất kỳ kiểu Serializable nào. Nhưng nếu bạn chọn sử dụng thuộc tính kiểu String làm ID, bạn sẽ có lợi ích là Mongo sẽ tự động gán giá trị cho nó khi được lưu (miễn là nó đang là null). Bằng cách chọn String, bạn nhận được việc gán ID do cơ sở dữ liệu quản lý và không cần phải gán giá trị cho thuộc tính đó một cách thủ công.

Mặc dù có một số trường hợp nâng cao hoặc ít gặp yêu cầu ánh xạ bổ sung, nhưng bạn sẽ thấy rằng trong phần lớn các trường hợp, @Document@Id, kết hợp với @Field hoặc @Transient khi cần, là đủ để thực hiện ánh xạ với MongoDB. Và chúng hoàn toàn phù hợp với các kiểu miền trong Taco Cloud.

Giờ đây, việc còn lại chỉ là viết các interface repository.

Released under the MIT License.