2

I have a problem where I evict an entity, but changes made to it will still change the database. This is part of the method in my DAO.

@Entity
public class Profile {
    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "PROFILE_ID")
    @LazyCollection(LazyCollectionOption.FALSE)
    private List<Avatar> avatars;

    ...
  }

In a DAO method:

Profile profile = getProfile(...);

// Clear from hibernate
currentSession.evict(profile);
profile.setId(null);

for (Avatar a : profile.getAvatars()) {
    currentSession.evict(a);
    a.setId(null);
}

currentSession.save(profile); // save a copy of Profile (not update)

before:

PUBLIC.PROFILE
  ID, DOMAIN, STATUS
  1, "test", "MEMBER"

PUBLIC.AVATAR
  ID, NAME, PROFILE_ID
  1, "main", 1

after method

PUBLIC.PROFILE
  ID, DOMAIN, STATUS
  1, "test", "MEMBER"
  2, "test", "MEMBER"

PUBLIC.AVATAR
  ID, NAME, PROFILE_ID
  1, "main", null
  2, "main", 2

So as you can see, the original row in AVATAR has now a null foreign key.

Why? This is happening in a unit / integration test using Unitils and Spring and this might influence how the Hibernate DAO works, maybe.

It's all in a in-memory H2 database..


After adding a line

profile.setAvatars(new ArrayList<>(profile.getAvatars());

it works ...

So I guess the problem was Hibernate's implementation of List, but how could that affect the behavior??

Merchuk Hul
  • 245
  • 1
  • 2
  • 11
  • Could you ask hibernate to show the SQL statements, maybe it could give an idea ... – Serge Ballesta Jul 15 '14 at 12:14
  • I did, and I can see clearly, that it inserts the second Profile (with Avatars) and then explicitly calls `update Avatar set profile_id = null where profile_id = 1;` ... – Merchuk Hul Jul 15 '14 at 12:21

1 Answers1

1

EDIT : First answer was stupid because of @LazyCollection(LazyCollectionOption.FALSE)

I could reproduce and fix but I cannot understand what actually happens under the hood ...

First what happens (spyed under debugger):

As avatars collection is eager, profile.getAvatars() is fully populated and is a Hibernate collection (in my own tests, it is a PersistentBag)

When profile is evicted, all its avatars are also evicted (at least with Hibernate 4.1.9 Final).

On currentSession.save(profile) all is marvelous, a new Profile is inserted and also a new Avatar. But on following transaction commit, Hibernate decides to do the famous update Avatar set profile_id = null where profile_id = 1; :-(

Next the fix :

I supposed that Hibernate is surprised to find a new entity already having a PersistentBag to carry a collection. So I created a simple ArrayList, appended current Avatars to it, and put it into profile :

List<Avatar> avatars = new ArrayList<Avatar>();
for (Avatar a : profile.getAvatars()) {
    currentSession.evict(a); // in fact useless but harmless
    a.setId(null);
    avatars.add(a);
}
profile.setAvatars(avatars);

And ... all is fine, Hibernate no longer emits the offending update !

So the cause seems to be a PersistentBag in a new entity, but i cannot imagine what actually happens in Hibernate internals.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • The collection is eager and not empty. I think I tried switching the order of eviction and this didn't change a thing ;-( – Merchuk Hul Jul 15 '14 at 11:50
  • If you want to create a new entity you should really call new ...() and copy the neccessary properties to the new one if needed. BeanWrapper can help you here. Hibernate is a powerful but tricky beast :) I for myself never change a collection, don't allow a set...() call directly to replace a collection. Better call clear and addAll. – Martin Frey Jul 15 '14 at 16:55
  • @SergeBallesta Thanks for the solution, but I worked it out myself yesterday and you seem to have retyped my work-around from the question ;-) Didn't you see it? – Merchuk Hul Jul 16 '14 at 05:19
  • @MartinFrey Why should I call `new` ? Any reasons other than conventions? – Merchuk Hul Jul 16 '14 at 05:19
  • 1
    @user3733425 It seems that hibernate does not really like that a user modifies an evicted entity and persists it later as a new entity. God only knows (and perhaps hibernate core devs ...) if it won't break in a future release of hibernate or in a similar but slightly different context. **I** really do not know the real reasons of the break and of the fix. IMHO it is more robust to stick to the standard use : new entity -> persist - evict -> merge. But you are on your own ;-) ... – Serge Ballesta Jul 16 '14 at 05:57
  • 1
    @user3733425 Serge wrote it quite nicely. You can manage to reuse entities to create new ones but you have to be extremely careful with cleaning all ids and collections have some other behaviors that are irritating :) bidirectional bindings need to be properly updated and a collection should never be replaced completely. Else you run into "delete all - update all" issues for example. I just dont evict after a persist. If you create links properly there is no need for that and you save a select query for performance. (bidirectional relations need to be set properly) – Martin Frey Jul 17 '14 at 06:23