当更新父对象时,Hibernate没有更新子对象

3
我有两个表,parent和child之间有一个@oneToMany的关系。以下是我的表结构。
表结构:
CREATE TABLE `parent` (
  `id_parent` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id_parent`)
)

CREATE TABLE `child` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `parent_id` int(3) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_parent_child` (`group_id`),
  CONSTRAINT `fk_parent_child` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id_parent`)
)

我已经创建了以下实体类:
父类。
@Entity
@Table(name = "parent")
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_parent")
    private int id;

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

    @OneToMany(mappedBy = "defaultchild", fetch = FetchType.EAGER)
    @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
    private Set<Child> childs;

    //setter and getters.
}
子类。
@Entity
@Table (name = "child")
public class Report {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column (name = "id")
    private int id;
    @Column (name = "name")
    private String name;

    @ManyToOne
    @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,org.hibernate.annotations.CascadeType.MERGE})
    @JoinColumn(name="parent_id")
    private Parent defaultchild;    

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "group",joinColumns={@JoinColumn(name="fk_child_id")},inverseJoinColumns={@JoinColumn(name="fk_group_id")})
    private Set<XXX> groups = new HashSet<XXX>(0);
    //Setter and Getter methods
}

Service Class

public UIGroup getParentByName(String name) {
    return DAO.getParentByName(name);
}

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public boolean updateParent(Parent parent) {
    return DAO.updateParent(parent);
}


@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public boolean deleteParent(Parent parent) {
    return DAO.deleteParent(parent);
}

DAO类。
public UIGroup getParentByName(String name) {
    Query query;
    Parent parent = null;
    try {
        String queryString = " from Parent where name = :name";
        query = sessionFactory.getCurrentSession().createQuery(queryString);
        query.setParameter("name", name);
        uiGroup = (Parent) query.uniqueResult();
    } catch (Exception e) {
        logger.error(e);
    }
    return parent;
}

public boolean updateParent(Parent parent) {
    boolean result = true;
    Session session = null;
    Transaction tx = null;
    try {
        session = sessionFactory.getCurrentSession();
        tx = session.beginTransaction();
        session.merge(parent);
        tx.commit();
        session.flush();
    } catch (HibernateException e) {
        result = false;
        logger.error(e);
    }// end of try-catch block.
    return result;
}


public boolean deleteParent(Parent parent) {
    boolean result = true;
    Session session = null;
    try {
        session = sessionFactory.getCurrentSession();
        session.delete(parent);
    } catch (HibernateException e) {
        result = false;
        logger.error( + e);
    }
    return result;
}

但是当我尝试调用以下代码时
Parent otherParent = Service.getParentByName("Other");
Parent parent = Service.getParentByName("XYZ");
//here I am assigning childs assign to XYX parent to other Parent
Set<Child> childs = new TreeSet<Child>(Child.COMPARE_BY_ID);
childs.addAll(otherParent.getchildes());
childs.addAll(parent.getchilde());
otherParent.setChilds(childs);
Service.updateParent(otherParent);
Service.deleteParent(parent);

我遇到了以下错误。
错误。
java.sql.BatchUpdateException: Cannot delete or update a parent row: a foreign key constraint fails (`database`.`child`, CONSTRAINT `fk_parent_child` FOREIGN KEY (`parent_id`) REFERENCES `child` (`id_parent`))

这意味着我的更新代码没有正常工作,以下是Service.updateParent(otherParent)语句的日志。
SELECT parent0_.id_parent AS id1_135_1_, parent0_.name AS name135_1_, childs1_.parent_id AS parent4_3_, childs1_.id AS id3_, childs1_.id AS id143_0_, childs1_.child_name AS child2_143_0_, childs1_.is_sea_child AS is3_143_0_, childs1_.parent_id AS parent4_143_0_ 
FROM ui_parent parent0_ 
LEFT OUTER JOIN child childs1_ ON parent0_.id_ui_parent=childs1_.parent_id 
WHERE parent0_.id_parent=1

请帮我,我不知道这段代码出了什么问题,提前谢谢。

这是 JPA 还是 Hibernate?我是想问你有一个带有 @Transactional 注释的 JPAish 服务层,但是你的 DAO 看起来似乎在单独管理事务。这是故意为之吗? - wallenborn
4个回答

3
如果您没有特定的原因选择限制@Cascade类型,您可以选择切换到ALL。
  @Cascade({ CascadeType.ALL})

甚至只是这样
  @OneToMany(cascade=CascadeType.ALL, mappedBy="defaultchild")

编辑:您几次拼写“parent”错误,请修复。


感谢您的快速回复。我确实尝试了您的代码,但是当我使用@OneToMany(cascade=CascadeType.ALL, mappedBy="defaultchild")或@Cascade({ CascadeType.ALL})时,子条目被删除了。 - Ashish Jagtap
实际上,我只是通过更改实体的名称来提供我的问题场景,这就是为什么有拼写错误的原因。 - Ashish Jagtap

3

我会写一个DAO方法 moveAllChildren(String srcName, String dstName) ,大概是这样的:

public class ParentDAO {
    @PersistenceContext
    private EntityManager em;

    public Parent findParentByName(name) {
      TypedQuery<Parent> q = em.createQuery("select p from Parent where p.name = :name", Parent.class);
      return q.setParameter("name", name).getSingleResult();
    }

    public void moveAllChildren(String srcName, String dstName) {
      Parent src = findParentByName(srcName);
      Parent dst = findParentByName(dstName);
      Set<Child> children = new HashSet<Child>();
      for (Child c: src.getChildren()) {
        children.add(c);
      }
      src.getChildren().removeAll(children);
      dst.getChildren().addAll(children);
    }
}

一般情况下,在使用级联操作时,最好明确地添加和删除子项,而不是说dst.setChildren(allChildren),这样可以让JPA有机会管理关系的双方。如果不这样做,您将面临一个风险,即子项仍然认为src是它们的父项,那么您很可能会看到数据库中的约束违规。
此外,尽量让JPA尽可能地管理实体内容是个好主意。因此,我宁愿不让应用程序调用服务来调用DAO来检索父项,只是为了移动他们的子项,然后再调用同样的服务来调用同样的DAO来合并这些更改到数据库中。最好在DAO级别上实现这个低级操作,然后添加一个服务来处理@Transaction。

@welleborn之前我只在DAO层使用了Transaction,但是它并没有对我的数据库产生任何改变。在阅读一篇文章后,我发现我必须使用**@Transactional,所以我在这里使用了@Transactional**。 - Ashish Jagtap
我尝试了上面给出的代码,将src中的所有子项移动到dst并更新两个父项,现在我遇到了“org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations)”错误。当我从“@ManyToOne”中删除“@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,org.hibernate.annotations.CascadeType.MERGE})”时,我仍然会得到相同的错误,即“a foreign key constraint fails”。 - Ashish Jagtap
你有选择的权利。你可以让你的DAO方法开始和提交事务,或者你可以将服务方法标记为@Transactional,让容器管理器处理细节。我认为后者更可取,因为它允许你使用DAO方法作为构建块,而不必担心它们干扰彼此的事务。但不要同时使用两种方法。选择其中一种即可。 - wallenborn
你不应该更新父级。在服务层中,只需有一个 '@Transactional' 方法,让 DAO 使用 '@PersistenceContext' 检索父级,移动子级,并让事务提交。Hibernate 将处理其余的事情。 - wallenborn
谢谢你的帮助,最终我找到了解决方案。以下是我所做的代码更改。 - Ashish Jagtap

0
最终,在Wallenborn和以下链接的帮助下,我找到了解决方案。 JPA not saving foreign key to @OneToMany relation 这是我在类中所做的更改。 实体类。
@Entity
@Table(name = "parent")
public class Parent {

    .....   

    @OneToMany(cascade = CascadeType.PERSIST,mappedBy = "defaultchild", fetch = FetchType.EAGER)
    private Set<Child> childs;

    //setter and getters.
}

@Entity
@Table (name = "child")
public class Child {

    ....

    @ManyToOne
    @JoinColumn(name="parent_id")
    private Child defaultchild; 

    //Setter and Getter methods
}

我已按照Wallenborn的建议更改了我的服务和DAO类,如下所示:

服务类。

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void moveAllChildren(String srcName, String dstName) {
    DAO.moveAllChildren(srcName, dstName);
} 

DAO类

public Parent findParentByName(name) {
      TypedQuery<Parent> q = em.createQuery("select p from Parent where p.name = :name", Parent.class);
      return q.setParameter("name", name).getSingleResult();
    }

    public void moveAllChildren(String srcName, String dstName) {
      Parent src = findParentByName(srcName);
      Parent dst = findParentByName(dstName);
      Set<Child> children = new HashSet<Child>();
      for (Child c: src.getChildren()) {
        children.add(c);
    }
    src.getChildren().removeAll(children);
    dst.getChildren().addAll(children);
}

0

您正在尝试删除parent,但它可能仍然有childs。在删除之前,请尝试清空childs集合:

for(Child child : parent.getChilds()){
    child.setParent(null);
}
parent.getChilds().clear();
Service.deleteParent(parent);

如果一个Child仍然引用了它,你就不能删除parent


谢谢您的回复,我按照上面给出的代码尝试了一下,但仍然出现了如下错误:无法删除或更新父行:外键约束失败 - Ashish Jagtap
这条消息告诉我parent上仍然有一个引用。尝试在数据库中查找它。你的代码使用了2个父级:otherParentParent。尝试将这两个调用分开,让它变得尽可能简单。那样会更容易些。 - Balázs Németh
在 for 循环中,我做了一些更改,例如我也更新了每个子实体,比如 child.setParent(otherParent);Service.updateChild(child),我检查了日志信息,并且它显示了更新查询,但是当我在数据库中检查时,实体还没有被更新。 - Ashish Jagtap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接