3.3.2 注解领域实体
正如您已经在 Spring Data JDBC 中看到的,Sprin gData 在运行时做了一些惊人的事情,自动创建存储库实现。但不幸的是,这对领域对象添加 JPA 注解并没有多大帮助。您需要打开 Ingredient、Taco 和 TacoOrder 类,并添加一些注解。首先是 Ingredient 类:
程序清单 3.18 为 JPA 持久化注解 Ingredient 类
package tacos;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@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
}
}
为了将其声明为 JPA 实体,Ingredient 类必须使用 @Entity
注解。它的 id 属性必须使用 @Id
进行注解,以便将其指定为惟一标识数据库中实体的属性。注意,这是来自 javax.persistence 包的 @Id
注解,而不是 org.springframework.data.annotation 包中的 @Id
注解。
还要注意,我们不再需要 @Table
注解,也不需要实现 Persistable。虽然我们仍然可以在这里使用 @Table
注解,但在使用 JPA 时是不需要这么做的,表名默认设置为类的名称(本例中为“"Ingredient”)。至于 Persistable,它只需要在使用 Spring Data JDBC 时,来确定是否要创建新实体还是更新现有实体。JPA 会自动测别出来。
除了特定于 JPA 的注解之外,还在类级别上添加了 @NoArgsConstructor
注解。JPA 要求实体有一个无参构造函数,所以 Lombok 的 @NoArgsConstructor
实现了这一点。但是要是不想使用它,可以通过将 access 属性设置为 AccessLevel.PRIVATE 来将其设置为私有。因为必须设置 final 属性,所以还要将 force 属性设置为 true,这将导致 Lombok 生成的构造函数,依据属性类型的不同会将它们设置为 null、0 或者 false。
您还将添加一个 @AllArgsConstructor
,以便于使用所有属性初始化值创建 Ingredient 对象。
还添加了一个 @RequiredArgsConstructor
。@Data
隐式地添加了一个必需的有参构造函数,但是当使用 @NoArgsConstructor
时,该构造函数将被删除。显式的 @RequiredArgsConstructor
确保除了私有无参数构造函数外,仍然有一个必需有参构造函数。
现在让我们转到 Taco 类,看看如何将其注解为 JPA 实体。
程序清单 3.19 把 Taco 注解为实体
package tacos;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
@Entity
public class Taco {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long 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")
@ManyToMany()
private List<Ingredient> ingredients = new ArrayList<>();
public void addIngredient(Ingredient ingredient) {
this.ingredients.add(ingredient);
}
}
与 Ingredient 一样,Taco 类现在使用 @Entity
注解,其 id 属性使用 @Id
注解。因为依赖于数据库自动生成 id 值,所以还使用 @GeneratedValue
注解 id 属性,指定自动生成策略。
要声明 Taco 及其相关 Ingredient 列表之间的关系,可以使用 @ManyToMany
注解 ingredient 属性。一个 Taco 可以有很多 Ingredient,一个 Ingredient 可以是很多 Taco 的一部分。
最后,让我们将 Order 对象注解为一个实体。下一个程序清单展示了新的 Order 类。
程序清单 3.20 把 Order 注解为 JPA 实体
package tacos;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import lombok.Data;
@Data
@Entity
public class TacoOrder implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date placedAt = new Date();
...
@OneToMany(cascade = CascadeType.ALL)
private List<Taco> tacos = new ArrayList<>();
public void addTaco(Taco taco) {
this.tacos.add(taco);
}
}
如您所见,对 Order 的更改与对 Taco 的更改非常相似。值得注意的是,与 Taco 对象列表的关系用的是 @OneToMany
注解,表示玉米卷都是针对这一订单的。此外,级联参数设置为 CascadeType.ALL,这样,如果订单被删除,其相关的玉米卷也将被删除。