JPA独特条目

时间:2016-07-23 16:49:23

标签: java hibernate jpa orm

我想为食谱分配一个类别。如果我为配方分配了第二个具有相同名称的类别,它会对数据库中的另一个插入进行中止(由于约束违规导致中止(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
);

2 个答案:

答案 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是预先存在的管理实体,则持久化操作会忽略它。