Hibernate not updating child object while updating parent object

25,231

Solution 1

I would probably just write a DAO method moveAllChildren(String srcName, String dstName), like this:

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);
    }
}

In General, when using Cascading, it is Good Practice to add and remove the children explicitly, rather than to say dst.setChildren(allChildren), to give JPA a chance to manage both sides of the Relation. If you don't do that, you risk that a child still thinks src is their parent, and then you'll likely to see a constraint violation from your database.

Additionally, it is a Good Idea to try to let JPA manage as much of your entity stuff as possible. So i'd rather not have the application call a service call a DAO to retrieve the parents only to move their children and then call the same service call the same DAO to merge these changes into the database. You're better off implementing this as a low-level operation on the DAO level and then add a service that calls that to handle the @Transaction.

Solution 2

If there is no specific reason why you chose to limit your @Cascade type, you can choose to switch to ALL.

  @Cascade({ CascadeType.ALL})

Or even just

  @OneToMany(cascade=CascadeType.ALL, mappedBy="defaultchild")

EDIT: you have misspelled 'parent' a few times, it won't hurt to fix that either.

Share:
25,231
Ashish Jagtap
Author by

Ashish Jagtap

Updated on March 12, 2020

Comments

  • Ashish Jagtap
    Ashish Jagtap about 4 years

    I have two tables parent and child has @oneTomany relationship between them. following is my table structure.

    Table Structure

    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`)
    )
    

    I have created Entity classes for this as follows

    Parent Class.

    @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.
    }
    

    Child Class.

    @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 Class.

    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;
    }
    

    but when I am trying to invoke following code

    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);
    

    I am getting following error.

    Error

    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`))
    

    which means my update code is not working properly following is log of Service.updateParent(otherParent) statement

    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
    

    please help me I don't know what went wrong with this code thanks in advance.

  • Ashish Jagtap
    Ashish Jagtap about 11 years
    thanks for the quick reply. I did try your code but child entries were getting deleted, when I used @OneToMany(cascade=CascadeType.ALL, mappedBy="defaultchild") or @Cascade({ CascadeType.ALL})
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    actually I am just giving a scenario of my problem by changing the names of an entities that's why this spelling mistakes are there.
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    thanks for your reply I tried code as given above but it still giving me an above error of Cannot delete or update a parent row: a foreign key constraint fails
  • Balázs Németh
    Balázs Németh about 11 years
    The message says to me that there is still a reference on parent. Try to look for that in the DB. And your code does work with 2 Parents: otherParent and Parent. Try to separate the two calls, make it as simply as possible. It would be easier that way.
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    in for loop i have made some changes like I am updating each child entity too like child.setParent(otherParent); Service.updateChild(child) and I checked log and it is gives update query but when i check in database the entries still not updated.
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    @welleborn previously I am using Transaction in DAO layer only,but it is not make any change in my database, After reading one article I found that I have to use @Transactional so that's why I used @Transactional here.
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    I tried code given above and move all children from src to dst and update both parents now I am getting org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations) error and when I am removed @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,‌​org.hibernate.annota‌​tions.CascadeType.ME‌​RGE}) from @ManyToOne I am getting same error mention above of a foreign key constraint fails
  • wallenborn
    wallenborn about 11 years
    You have the choice. You can let your DAO methods begin and commit transactions, or you can mark the service methods as @Transactional and have your container manager handle the details. The latter method is imo prefereable since it allows you to use DAO methods as building blocks without having to worry about them interfering with each others' transactions. But don't use both. Pick one.
  • wallenborn
    wallenborn about 11 years
    And you shouldn't have to update the parents. Just have a '@Transactional' method in the service layer, let the DAO use the '@PersistenceContext' to retrieve the parents, move the children around, and let the transaction commit. Hibernate will take care of the rest.
  • Ashish Jagtap
    Ashish Jagtap about 11 years
    Thanks for your help finally i got solution following are the code changes that I have made.