Search code examples
javahibernatejpahibernate-mapping

HibernateException when updating a collection configured with delete orphan : can't save the parent object


I work on a Java project and I have to write a new module in order to copy some data from one database to another (same tables).

I have an entity Contrat containing several fields and the following field :

@OneToMany(mappedBy = "contrat", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
@Cascade( { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
@BatchSize(size = 50)
private Set<MonElement> elements = new HashSet<MonElement>();

I must read some "Contrat" objects from a database and write them in another database.

I hesitate between 2 solutions :

  • use jdbc to query the first database and get the objects and then write those objects into the second database (paying attention to the order and the different keys). It will be long.
  • as the project currently uses Hibernate and contains all hibernate mapping classes, I was thinking about opening a first session to the first database, reading the hibernate Contrat object, setting the ids to null in the children elements and writing the object to the destination database with a second session. It should be quicker.

I wrote a test class for the second use case and the process fails with the following exception :

    org.hibernate.HibernateException: Don't change the reference to a 
    collection with cascade="all-delete-orphan"

I think the reference must change when I set the ids to null, but I am not sure : I don't understand how changing a field of a Collection member can change the Collection reference

Note that if I remove DELETE_ORPHAN from the configuration, everything works, all the objects and their dependencies are written in the database. So I would like to use the hibernate solution which is faster but I have to keep the DELETE_ORPHAN feature because the application currently uses this feature to ensure that every MonElement removed from the elements Set will be deleted in the database. I don't need this feature but cannot remove it.

Also, I need to set the MonElement ids to null in order to generate new ones because their id in the first database may exist in the target database.

Here is the code I wrote which works well when I remove the DELETE_ORPHAN option.

    SessionFactory sessionFactory = new AnnotationConfiguration().configure("/hibernate.cfg.src.xml").buildSessionFactory();
    Session session = sessionFactory.openSession();
    // search the Contrat object
    Criteria crit = session.createCriteria(Contrat.class);
    CriteriaUtil.addEqualCriteria(crit, "column", "65465454");
    Contrat contrat = (Contrat)crit.list().get(0);

    session.close();

    SessionFactory sessionFactoryDest = new AnnotationConfiguration().configure("/hibernate.cfg.dest.xml").buildSessionFactory();
    Session sessionDest = sessionFactoryDest.openSession();
    Transaction transaction = sessionDest.beginTransaction();

    // setting id to null, also for the elements in the elements Set
    contrat.setId(null);
    for (MonElement element:contrat.getElements()) {
        element.setId(null);
    }
    // writing the object in the database
    sessionDest.save(contrat);
    transaction.commit();
    sessionDest.flush();
    sessionDest.close();

This is way faster than managing myself the queries and the primary / foreign keys and dependencies between objects.

Does anyone have an idea to get rid of this exception ? Or maybe I should change the state of the Set. In fact I'm not trying to delete any element of this Set, I just want them to be considered as new objects.

If I don't find a solution, I will do something dirty : duplicate all hibernate entity objects in my new project and remove the DELETE_ORPHAN parameter in the newly created Contrat. So the application will continue using its mapping and my new project will use my specific mapping. But I want to avoid that.

Thanks


Solution

  • A correct solution has been written by crizzis as a comment to my question. I quote him :

    I'd try wrapping the contrat.elements in a new collection (contrat.setElements(new HashSet<>(contrat.getElements())) before trying to persist the contract with the new session

    It works well.