views:

43

answers:

1

Hello.

I have a database in which an entity (say, User) has a list of entities (say, List). As a requirement, I must have a denormalized count of the entities on the list:

@Entity
class User {
    /* ... */
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Friendship> friends;

    public int friendsCount;
    /* ... */
}

When I add a new element to the list, I must, transactionally, increase the corresponding counter.

I tried to do so using an EntityListener:

class MyBrokenEntityListener {
    @PostPersist
    public void incrementCounter(Friendship friendship) {
        friendship.getUser1().incrementFriendsCount();
    }
}

The entity listener is being correctly called, and while debugging I can see that it correctly modifies the entity. However, Hibernate sends no UPDATE query to the database, only the INSERT query (corresponding to the Friendship entity).

I have tried different events on the EntityListener, but it does not seem to work.

What I suppose is happening here is that the entity listener is triggered by an update to the a User. It then identifies all the dirty properties of the user, which consist of the List alone. Therefore it has nothing to do about the User, it has only to insert a Friendship. It then cascades the operation to the Friendship. The entity listener is called, it modifies the user, but at this time Hibernate has already determined that it had not to worry about the user, therefore it does not update the user.

Is this reasoning correct?

If so, how can I properly achieve this?

Thanks, Bruno

A: 

I have tried different events on the EntityListener, but it does not seem to work.

From what I can see, the one-to-many association between User and Friendship is bidirectional but your mapping doesn't reflect this. So could you fix your mapping first? Something like this:

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

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, 
               mappedBy = "user1")
    private List<Friendship> friends = new ArrayList<Friendship>();

    public int friendsCount;

    // omitting getters, setters for brevity

    public void incrementFriendsCount() {
        friendsCount++;
    }

    public void addToFriends(Friendship friend) {
        this.getFriends().add(friend);
        friend.setUser1(this);
    }
}

Notable changes:

  • I've added a mappedBy on the non owning side of the association
  • I've added a convenient method to set both sides of the link correctly when adding a Friendship

The entity listener is called, it modifies the user, but at this time Hibernate has already determined that it had not to worry about the user, therefore it does not update the user.

Your MyBrokenEntityListener listener works for me, I couldn't reproduce the issue using the above fixed mapping: Hibernate does detect that User instance is stale after the Listener invocation and it does trigger an UPDATE. The following test method (running inside a transaction) passes:

@Test
public void testPostPersistInvocationOnAddFriend() {
    User1 user = new User1();
    Friendship friend1 = new Friendship();
    user.addToFriends(friend1);
    em.persist(user);
    em.flush();
    assertEquals(1, user.getFriendsCount());

    Friendship friend2 = new Friendship();
    user.addToFriends(friend2);
    em.flush();

    em.clear(); // so that we reload the managed user from the db
    user = em.find(User1.class, user.getId());
    assertEquals(2, user.getFriendsCount());
}
Pascal Thivent
Hi, Pascal. Actually, I had the "mappedBy" parameter on my real code, just forgot to write on the example here. I will try it again, and keep you informed. Thanks!
Bruno Reis
@Bruno Did you also set both sides of the link correctly when adding a Friend? I used Hibernate 3.5.x by the way.
Pascal Thivent