我想为食谱分配一个类别。如果我为配方分配了第二个具有相同名称的类别,它会对数据库中的另一个插入进行中止(由于约束违规导致中止(UNIQUE约束失败:category.name) - 这实际上很好)。我想重复使用此条目并将其附加到配方中。是否有JPA方式“自动”执行此操作或我必须处理此问题?我应该在setCategory方法中搜索具有相同名称的类别并使用此类别(如果存在)吗?有模式吗?
@Entity
public class Recipe {
private Integer id;
private Category category;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "category_id", referencedColumnName = "id")
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
@Entity
public class Category {
private Integer id;
private String name;
private List<Recipe> recipes;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Basic
@Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToMany(mappedBy = "category", cascade = {CascadeType.ALL})
public List<Recipe> getRecipes() {
return recipes;
}
public void setRecipes(List<Recipe> recipes) {
this.recipes = recipes;
}
}
示例:
Category category = new Category();
category.setName("Test Category");
cookbookDAO.add(cookbook);
Recipe recipe = new Recipe();
recipe.setTitle("Test Recipe");
recipe.setCategory( category );
recipeDAO.add(recipe);
执行此操作两次会导致UNIQUE约束失败:category.name。这很好,因为我不想要多个具有相同名称的类别。数据库强制执行此操作但我正在寻找解决方案以在java语言级别上强制执行此操作。 DDL:
CREATE TABLE "recipe"
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER,
FOREIGN KEY (category_id) REFERENCES category(id)
);
CREATE TABLE "category"
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR,
UNIQUE(`name`) ON CONFLICT ABORT
);
答案 0 :(得分:0)
您所描述的行为是映射的结果
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "category_id", referencedColumnName = "id")
public Category getCategory() {
return category;
}
如果我们翻译这个映射是简单的语言。你在说:
每当我尝试保存配方时,相应的类别应该 坚持下去。
这里的问题来自于您已经拥有现有类别,因此@ Cascade.PERSIST不合适。
这里的语义恰恰相反。仅当类别尚不存在时,Recipie才会创建类别。这意味着类别的创建不仅仅是一般规则的例外。
这意味着你有两个选择。
选项1 是删除Cascade.PERSIST。
选项2 是将其替换为Cascade.MERGE。
选项3 是走另一条路。而不是使用Cascade.PERSIST将Recipe中的@ManyToOne关系注释到Category以注释从Category到Recipe的@OneToMany关系。
这种方法的优点很简单。虽然在添加新配方时并不总是想要创建新类别。您希望添加新类别的所有时间都是100%,您还要添加所有附加的Recipies。
此外,我建议您支持双向关系而不是单向关系,并阅读本文关于合并和持久JPA EntityManager: Why use persist() over merge()?
答案 1 :(得分:0)
问题是您正在使用以下语句创建新类别:
Category category = new Category();
因为Category
实体的这个实例是新的并且因此没有持久标识,所以持久性提供程序将尝试在数据库中创建新记录。由于name
表的category
字段为唯一,您会从数据库中获得约束违规异常。
解决方案首先获取类别并将其分配给recipe
。所以你需要做的是:
String queryString = "SELECT c FROM Category c WHERE c.name = :catName";
TypedQuery<Category> query = em.createQuery(queryString, Category.class);
em.setParameter("catName", "Test Category");
Category category = query.getSingleResult();
此获取的实例是一个托管实体,持久性提供程序不会尝试保存。然后像您一样将此实例分配给recipe
:
recipe.setCategory( category );
在这种情况下,级联将忽略在保存category
时保存recipe
实例。这在JPA 2.0规范的 3.2.2持久化实体实例一节中说明如下:
如果X是预先存在的管理实体,则持久化操作会忽略它。