views:

4476

answers:

2

I am using JPA with Hibernate underneath, and I am having trouble getting merge to work, but before I describe the problems I am encountering with JPA, let me lay out what I am trying to accomplish, just in case my problems stems from my approach.

I have data from on system that I need to place into another system. To do this I am reading the data then constructing new ORM objects based on that data, I then want to persist the ORM objects into the database. Now when the database is empty, the program works without issue, with a simple em.persist(object) call. However I want to be able to run this process when the database has data in it, adding new data and updating old data as necessary, this is where I am having issues.

I do a simple check to see if the item already exists in the database, if nothing is found, I persist, if the record exists, I attempt a merge. Which fails with a duplicate record error;

ERROR JDBCExceptionReporter - Violation of UNIQUE KEY constraint 'UK-SubStuff-StuffId-SubStuffNumber'. Cannot insert duplicate key in object 'SubStuff'.

It strikes me as strange that the em.merge() call is attempting a insert instead of a update (I confirmed this through SQL logging).

Hibernate: insert into SubStuff (SubStuffNumber, StuffId, Name, TypeId) values (?, ?, ?, ?)

I should note that I am using objects that cascade into sub-objects. The failure is occurring on a sub-object, which makes sense to me as I would expect it to try and merge the sub-objects first.

Below is my code, please excuse the rough state, I've tried to document a few workaround approaches here.

private void storeData(Collection<Stuff> Stuffs) {

    for (Stuff stuff : Stuffs) {
  //I think this first block can be safely ignored, as I am having no issues with it
  //  Left it in just in case someone more experianced then I sees the root of the issue here.
        Collection<SubStuff> subStuffs = stuff.getSubStuffCollection();
        for (SubStuff s : subStuffs) {
            //Persist SubStuff Type, which DOES NOT cascade,
            //  due to it not having an internal SubStuff collection
            Query q = em.createNamedQuery("SubStuffType.findByType");
            q.setParameter("type", f.getTypeId().getType());
            try {
                SubStuffType sst = (SubStuffType) q.getSingleResult();
                s.setTypeId(sst);
            } catch (NoResultException ex) {
                if (logger.isDebugEnabled()) logger.debug("SubStuff Type not found, persisting");
                em.persist(s.getTypeId());
            }
        }

        if (em.find(Stuff.class, stuff.getId()) == null) {
            //Persist on Stuffs will cascade to SubStuffs
            em.persist(stuff);
        } else {
            //  Failing to merge SubStuff, tries to insert duplicate
            //  Merge SubStuff first
   // The block below is my attempt to merge the SubStuff Collection before merging Stuff,
   //  it creates the same isuse as a straight merge of Stuff.
            Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
            for (SubStuff s : SubStuffs) {
                Query q = em.createNamedQuery("SubStuff.findBySubStuffNumberStuffId");
                q.setParameter("SubStuffNumber", s.getSubStuffNumber());
                q.setParameter("StuffId", stuff.getId());
                try {
                    SubStuff subStuff = (SubStuff) q.getSingleResult();
  // -----> Merge fails, with an duplicate insert error
                    SubStuff mergedSubStuff = em.merge(s);
                    mergedSubStuffs.add(mergedSubStuff);
                } catch (NoResultException ex) {
                    throw ex;
                }
            }
            stuff.setSubStuffCollection(mergedSubStuffs);

  // -----> This will fails with same error as above, if I remove the attempt
   // to merge the sub objects
            em.merge(stuff);
        }
    }
}

If anyone with JPA experience can help me out I would really appreciate it. The differances between Hibernate's saveOrUpdate() and JPA's merge() are obviously tripping me up, but despite reading several articles on EnityManger's merge, I still can't wrap my head around what is going on here.

Thanks for your time.

A: 

Your problem is with the following couple of lines:

Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
...
stuff.setSubStuffCollection(mergedSubStuffs);

JPA will consider new collection as complete new set of sub-entities and will always insert them as such. Keep working with original collection of SubStuff within Stuff entity and you'll be fine.

grigory
It actually doesn't even get to the second line there. It fails on "SubStuff mergedSubStuff = em.merge(f);" (wish SO supported code line numbers). Your advice is probably going to be useful in the future though, thank you
James McMahon
The mergedSubStuff collection can be removed altogether from the code, it is supercilious. The import thing is the my SubStuff be assigned the correct Primary Key, then it will be merged correctly by the cascading done with the call to em.persist(stuff).
James McMahon
"supercilious"? Superfluous.
James McMahon
+1  A: 

The curse of StackOverflow has struck again. After working on this problem for about a day I decide to post this question, within 20 minutes I had an eureka moment and solved it. Partly because I had clarified my thoughts enough to post the question. In thinking about what information might be pertinent to the question I realized that my auto generated keys were to blame (or my correctly, my stupid non-handling of them on a merge).

My issue is that SubStuff (worst substitute naming scheme, sorry about that) has an autogenerated artificial primary key. So when merging I needed to do;

SubStuff subStuff = (SubStuff) q.getSingleResult();
//++++++++
s.setId(subStuff.getId());
//++++++++

//The following code can be removed.
//--- SubStuff mergedSubStuff = em.merge(f);
//--- mergedSubStuffs.add(mergedSubStuff);

This sets the primary key to the row that already exists in the database and upon initial testing seems to work fine.

The call to merge can be simplified down to just the call to em.merge(stuff) as that will cascade the substuff objects and the mergedsubstuff collection can be removed all together as we are no longer doing a merge inside that loop.

Thanks to anyone who read through my ridiculously long question, hopefully my question will be useful to someone in the future.

James McMahon
I am glad you have it figured out.
grigory