9

I have the following JSON:

{
    id: 123,
    subObjects: [
        {
            id: 564,
            name: "foo",
            contry: {
                id: 1,
                name: "Germany"
            }
        },
        {
            id: 777,
            name: "bar",
            contry: {
                id: 1,
                name: "Germany"
            }
        }
    ]   
}

And deserialize it using Gson. After that I need to merge the JPA Entity:

Model model = new Gson().fromJson(json, modelClass);
model = entityManager.merge(model)

The refresh is cascaded from the model to the subObjects and down to the countries. This causes the Exception "An entity copy was already assigned to a different entity" by Hibernate.

It works if I use different countries. It works if I use copy the country instance from one object into the other one so both subObjects references the same instance of that contry.

Both countries have identical values in it. Both have the same hashCode, too. The both countries are equal but not == since they are different instances.

The tipps listed on this question did not help me.

I am using Hibernate 4.1.3 Final and Gson 2.2

java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
    at org.hibernate.event.internal.EventCache.put(EventCache.java:184)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
    at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
    at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
    at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
    at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874)
    at play.db.jpa.GenericModel.merge(GenericModel.java:234)
    [...]

How can I solve this problem in a generic way I don't need to know the object types and which objects are logically identical?

Community
  • 1
  • 1
  • @AndreiI Upgrading is not an option since the framework I am using (play 1.2.x) requires the specific version because of patches they use for it. I really need to solve it in a generic way because I have the same case with different Entities and yes I need to transport big objects from a webserver to a mvc-javascript-framework (AngularJS) on the client side and store them back to the server. The objects are representing complex corporate data. –  Nov 27 '13 at 10:50
  • Have you thought what should be the behaviour when two countries with the same ID, but different names come? Because this question alone is not easy to answer, I would propose you not to accept everything from the interface, but rather use DTOs that load the IDs. Also, you probably trust too much to the user interface. Simply think of adding a country with an inexisting ID or a country that is not allowed to be used in that interface. That means you have to validate the data and not simply merge it in DB. – V G Nov 27 '13 at 10:58
  • The Users of this application are 100% trusted administrative users without the needed knowledge for modifying the data in a unusual way but yes, validation is needed. In my cases they do not need to modify the contries but they need to select the right one. Other cases may be much more compilated to implement. For my Case it probably would be enougth to get the ID of the contries in that object and hibernate could do the rest, but only if they are the same instance. –  Nov 27 '13 at 11:24
  • The behavior when different values are stored in the objects with same ID should be throwing an exception because in my case this is invalid data and so a bad request. –  Nov 27 '13 at 11:31
  • 1
    I think such behaviors are simply out of scope of JPA or Hibernate, and that's why Hibernate will throw that Exception: because it does not know what to do. What I want to say, is that you should avoid equal instances in your entity graph, either interfering on the GSON's level (which also seems to not support such a feature) or just before merging the entity graph in DB. – V G Nov 27 '13 at 11:48
  • That this is out of scope of JPA is very clear to me. Yes Gson does not support anything like that too. It does not make much sence in a general point of view. Yust because two objects have the same values in in the moment does not mean they should be the same instances. You may want to change on object without modifying the other one. I know I really need to do something before merge but I have no idea how to solve this in a clear generic fashion. –  Nov 27 '13 at 12:15
  • I've invented such a wheel once. I didn't use built-in GSON json-to-object mapper. I created my own, which was able to merge such cases. Of course, it contained EM as a dependency and searched for objects by their ID before deserialization. It took abount 3 month at all. So here is your choice: reinvent this wheel once again or surrender and make json-to-entity mapping by hands. If I was to choose now, I would choose the latter. – Alexey Andreev Nov 28 '13 at 09:50

4 Answers4

2

I think such behaviors are simply out of scope of JPA or Hibernate, and that's why Hibernate will throw that Exception: because it does not know what to do. What I want to say, is that you should avoid equal instances in your entity graph, either interfering on the GSON's level (which also seems to not support such a feature) or just before merging the entity graph in DB.

V G
  • 18,822
  • 6
  • 51
  • 89
2

Here is my working solution not using merge:

I use reflection to iterate over the fields at binding time. I check if the field is an entity or a collection of entities and then call a findByID if the id if provided in the json or create a new instance of the entity if not to get an attached entity. After that I copy the field values that exists in the json via reflection into the attached entity. I do it recursively to handle relations between the entities. After that I have a completly merged and attached version of my entity with all that attached and merged subobjects. I can save it without any Problems

Important note:

Andrei I suggested a lot of logical problems that could occur and changing the api to not get the full countries, but just their IDs would be more clearly. I prefer this and will do this in the future. Therefore Andrei I earned the bounty.

1

Have you tried detached all entities using a clear method? I Think if you detached all entities you will be able to do a merge.

bcfurtado
  • 181
  • 2
  • 12
  • 2
    When deserializing all the objects at once from a single json, they all should be detached, shouldn't they? –  Nov 29 '13 at 21:10
  • helped in my case but i was working from a junit test – tibi Apr 20 '17 at 12:55
0

Someone opened a ticket time ago in the Hibernate Jira's to add more information when this error occurs

HHH-7605

The change was added in 4.2.x version, you can take a look at commit log on Hibernate's Git for EventCache for changes and EventCacheTest to reproduce the problem

HHH-7605 Event cache descriptive log messages

I hope this helps you to find the problem.

vzamanillo
  • 9,905
  • 1
  • 36
  • 56
  • Thanks, but upgrading hibernate is not an option. The framework I am using (play 1.2.x) uses a patched version of hibernate and I cannot simply use another hibernate version. –  Dec 03 '13 at 08:48
  • A Dozer mapper Git user solve this problem adding the @Transactional annotation to his tests class, https://github.com/DozerMapper/dozer/issues/109 – vzamanillo Dec 03 '13 at 08:50
  • Oh no, I am not suggesting you have to upgrade Hibernate, but you can take a look at the Tests code to identify the real problem. – vzamanillo Dec 03 '13 at 08:52