【问题标题】:IllegalStateException : a relationship that was not marked cascade PERSISTIllegalStateException :未标记级联 PERSIST 的关系
【发布时间】:2020-03-23 12:32:12
【问题描述】:

我正在尝试以一对多的方式在 PostgreSQL 数据库中保留两个实体类别和问题(类别有很多问题,一个问题属于一个类别)。

经过大量搜索和尝试,将 CascadeType.PERSIST 添加到这两个实体是我发现的唯一解决该错误的方法,但在问题方面使用 CascadeType.PERSIST 时,类别表中将充满重复项。有没有更好的解决方案,因为表中的类别应该是唯一的。

@Entity
@Table(name = "Category")
public class Category {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "CID")
  private int categoryId;
  @Column(name = "CNAME")
  private String categoryName;
  @OneToMany(mappedBy = "category" , cascade = CascadeType.PERSIST)
  public List<Question> questions;

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Category)) {
      return false;
    }
    Category category = (Category) o;
    return categoryId == category.categoryId
        && getCategoryName().equals(category.getCategoryName())
        && questions.equals(category.questions);
  }

  @Override
  public int hashCode() {
    return Objects.hash(getCategoryName());
  }
@Entity
@Table(name = "Question" )
public class Question {
  @Id
  @Column(name = "QID")
  private int id;
 @Column(name = "QText")
  private String question;
@ManyToOne()
  private Category category;



  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Question)) {
      return false;
    }
    Question question = (Question) o;
    return getId() == question.getId();
  }

  @Override
  public int hashCode() {
    return Objects.hash(getId());
  }

public persist(){
 EntityManager em = getEntityManager();
      em.getTransaction().begin();
      for (Category c : data.getCategories()) {
        em.persist(c);
      }
      em.getTransaction().commit();
      em.close();
}

【问题讨论】:

  • 您能否就您要解决的问题向我们提供更多信息:是IllegalStateException 异常还是the category table we'll be full of duplicates。如果是后者,您能否在此声明中添加更多说明?
  • 类别表应该有 n 个类别,并且问题表中的每个问题都应该使用外键指向它的类别,但我得到的是一个表(类别),其中每个问题的类别再次持久化,因此类别表具有作为问题的确切行数,而不是增加 n 行,这使得表中充满了重复项,我认为这是因为级联。是不是更清楚了?
  • 对我来说这不是cascade 问题:事实上cascade 是JPA 的一个方便功能,可以避免在您的情况下对子实体调用persist 方法Question 实体和许多其他方便的任务。对于您的问题,您能否提供更多有关persist 方法中的data 变量的信息,您在数据库中获得的内容以及您的期望?
  • persist 方法从包含许多类别的列表中获取一个类别对象(在上面的代码中)。我获得了应有的问题表和每个问题都有一行的类别表,因此它不使用问题表中的 fk (fk_categorieID)
  • 所以如果我理解你的问题,在data 变量中,你可以说 5 个Category,对于每个你有 3 个Question。您期望在Category 表中获得5 行,在Question 表中获得15 行。但是您在每个表中获得 15 行,不是吗?

标签: java algorithm hibernate jpa persistence


【解决方案1】:

我在本地机器上解决您的问题的唯一方法是将您的实体 ID 的类型:int 更改为 Long

基于您的代码的工作示例:

类别实体

package io.ahenteti.java;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

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

@Data
@Entity
@Table(name = "Category")
@AllArgsConstructor
@NoArgsConstructor
@NamedQueries({@NamedQuery(name = Category.FIND_ALL, query = "SELECT c FROM Category c")})
public class Category {

    public static final String FIND_ALL = "Category.findAll";

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "CID")
    private Long categoryId;

    @Column(name = "CNAME")
    private String categoryName;

    @OneToMany(mappedBy = "category", cascade = CascadeType.PERSIST)
    public List<Question> questions = new ArrayList<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Category)) {
            return false;
        }
        Category category = (Category) o;
        return categoryId == category.categoryId && getCategoryName().equals(category.getCategoryName()) && questions
                .equals(category.questions);
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCategoryName());
    }
}

问题

package io.ahenteti.java;

import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

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

@Data
@Entity
@Table(name = "Question")
@AllArgsConstructor
@NoArgsConstructor
@NamedQueries({@NamedQuery(name = Question.FIND_ALL, query = "SELECT q FROM Question q")})
public class Question {

    public static final String FIND_ALL = "Question.findAll";
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "QID")
    private Long id;
    @Column(name = "QText")
    private String question;
    @ManyToOne()
    private Category category;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Question)) {
            return false;
        }
        Question question = (Question) o;
        return getId() == question.getId();
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId());
    }
}

persistence.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence 
  xmlns="http://xmlns.jcp.org/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
  version="2.2">

  <persistence-unit name="persistence-unit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>io.ahenteti.java.Question</class>
    <class>io.ahenteti.java.Category</class>
    <properties>
      <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:demo"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

主要

package io.ahenteti.java;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        EntityManager em = Persistence.createEntityManagerFactory("persistence-unit").createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        em.persist(createCategory("C1"));
        em.persist(createCategory("C2"));
        em.persist(createCategory("C3"));
        transaction.commit();
        System.out.println("number of categories in database: " + getAllCategories(em).size());
        System.out.println("number of questions in database: " + getAllQuestions(em).size());
    }

    private static List<Question> getAllQuestions(EntityManager em) {
        TypedQuery<Question> getAllQuestions = em.createNamedQuery(Question.FIND_ALL, Question.class);
        return getAllQuestions.getResultList();
    }

    private static List<Category> getAllCategories(EntityManager em) {
        TypedQuery<Category> getAllCategories = em.createNamedQuery(Category.FIND_ALL, Category.class);
        return getAllCategories.getResultList();
    }

    private static Category createCategory(String category) {
        Category res = new Category();
        res.setCategoryName(category);
        for (int i = 0; i < 5; i++) {
            res.getQuestions().add(createQuestion(res, category + " - Q" + i));
        }
        return res;
    }

    private static Question createQuestion(Category category, String question) {
        Question q1 = new Question();
        q1.setQuestion(question);
        q1.setCategory(category);
        return q1;
    }

}

输出

number of categories in database: 3
number of questions in database: 15

希望对你有帮助:)

【讨论】:

    【解决方案2】:

    如果没有太多细节,就很难推断出问题的根源;因此,如果您想要完成的是保留与另一个实体具有一对多关联的实体集合,那么您肯定会以编程方式进行半蛮力操作,而无需使用注释配置来解决问题。

    首先从您的实体中删除所有CascadeType 注释。

    然后编写你的persist方法如下:

        public void persist(Collection<Category> categories) {
            EntityManager em = getEntityManager();
            em.getTransaction().begin();
    
            // Persist all categories first, without persisting the questions
            categories.forEach(cat -> safelyPersistCategory(em, cat));
            // Associated the not yet persisted questions with the persisted categories  
            categories.stream().forEach(cat -> cat.getQuestions().stream().forEach(question -> question.setCategory(cat)));
            // Finally persist each question
            categories.stream().flatMap(cat -> cat.getQuestions().stream()).forEach(question -> em.persist(question));
    
            em.getTransaction().commit();
            em.close();
        }
    
        // Safely persists Category without trying to create a relationship to 
        // potentially not persisted Questions
        private void safelyPersistCategory(EntityManager em, Category category) {
            List<Question> questions = category.getQuestions();
            category.setQuestions(null);
            em.persist(category);
            category.setQuestions(questions);
        }
    

    正如您在手动代码中的 cmets 中看到的:

    • Category 实体安全地添加到持久化上下文中(无需 将它们与潜在的非托管问题相关联)。
    • 然后将这些托管Category实体与其Question实体相关联。
    • 最后将Question 实体添加到持久化上下文中。

    您可以从那里提交事务。

    这个序列的原因是Question实体是关系的所有者,所以它必须与一个已经持久化/托管实体(Category)相关,否则它会尝试链接以创建与非持久/非托管实体的关联,这会导致问题(我怀疑这是您原来的IllegalStateException 发生的事情)。

    最后,通过首先持久化 Category 实体,您可以将 Question 实体链接到已托管的实体,而不是动态创建它们。

    Complete code on GitHub

    希望这会有所帮助。

    【讨论】:

    • 感谢您的回复。当我删除所有 CascadeType 时,我得到 IllegalStateException :通过未标记为 cascade PERSIST 的关系找到了一个新对象
    • 哦,是的;必须首先保留类别而不引用问题。我刚刚用这个考虑更新了答案
    猜你喜欢
    • 1970-01-01
    • 2021-09-22
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2017-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多