2

My domain model has a self-referencing bi-directional relationship with relationship management done in the entity:

@Entity
public class Users implements BaseEntity<String>, Serializable {

    @Id
    private String username;
    @ManyToMany(cascade = {CascadeType.REFRESH, CascadeType.MERGE, CascadeType.PERSIST})
    private List<User> associatedSenders;
    @ManyToMany(mappedBy = "associatedSenders")
    private List<User> associatedReceivers;

    //
    // Associated Senders
    //
    public List<User> getAssociatedSenders() {
        if (associatedSenders == null) {
            associatedSenders = new ArrayList<User>();
        }

        return associatedSenders;
    }

    public void addAssociatedSender(User sender) {
        if (associatedSenders == null) {
            associatedSenders = new ArrayList<User>();
        }

        associatedSenders.add(checkNotNull(sender));
        if (!sender.getAssociatedReceivers().contains(this)) {
            sender.addAssociatedReceiver(this);
        }
    }

    public void removeAssociatedSender(User sender) {
        if (associatedSenders == null) {
            associatedSenders = new ArrayList<User>();
        }

        associatedSenders.remove(checkNotNull(sender));
        if (sender.getAssociatedReceivers().contains(this)) {
            sender.removeAssociatedReceiver(this);
        }
    }

    public void setAssociatedSenders(List<User> senders) {
        checkNotNull(senders);

        if (associatedSenders == null) {
            associatedSenders = new ArrayList<User>();
        }

        // first remove all previous senders
        for (Iterator<User> it = associatedSenders.iterator(); it.hasNext();) {
            User sender = it.next();
            it.remove();
            if (sender.getAssociatedReceivers().contains(this)) {
                sender.removeAssociatedReceiver(this);
            }
        }

        // now add new senders
        for (User sender : senders) {
            addAssociatedSender(sender);
        }
    }

    //
    // Associated Receivers
    //
    public List<User> getAssociatedReceivers() {
        if (associatedReceivers == null) {
            associatedReceivers = new ArrayList<User>();
        }

        return associatedReceivers;
    }

    /**
     * <p><b>Note:</b> this method should not be used by clients, because it
     * does not manage the inverse side of the JPA relationship. Instead, use
     * the appropriate method at the inverse of the relationship.
     * 
     * @param receiver
     */
    protected void addAssociatedReceiver(User receiver) {
        if (associatedReceivers == null) {
            associatedReceivers = new ArrayList<User>();
        }

        associatedReceivers.add(checkNotNull(receiver));
    }

    /**
     * <p><b>Note:</b> this method should not be used by clients, because it
     * does not manage the inverse side of the JPA relationship. Instead, use
     * the appropriate method at the inverse of the relationship.
     *
     * @param receiver
     */
    protected void removeAssociatedReceiver(User receiver) {
        if (associatedReceivers == null) {
            associatedReceivers = new ArrayList<User>();
        }

        associatedReceivers.remove(checkNotNull(receiver));
    }
}

When I add new user entities to the associatedSenders collection, everything works as expected. The table in the db gets updated correctly and the in-memory relationships are correct, as well. However, when I remove a user entity from from associatedSenders collection (or all entities from that collection), e.g. by doing a call like this:

List<User> senders = Collections.emptyList();
user.setAssociatedSenders(senders)

the database table gets updated correctly, but the next call to em.find(User.class, username), where username is the id of the user who previously was in the associatedSenders collection, reveals that the associatedReceivers collection (the inverse side) has not been correctly updated. That is, user is still in that collection. Only if I refresh the entity via em.refresh(), the collection is correctly updated. Looks like the entity manager does some caching here, but this behavior seems incorrect to me.

UPDATE Probably it's worth mentioning that I'm modifying the user entity in the frontend within a JSF managed bean, i.e. while the entity is in the detached state.

Theo
  • 3,074
  • 7
  • 39
  • 54

2 Answers2

3

If you are modifying the object while it is detached, then you must merge() it back into the persistence unit. Since you are modifying the source and target objects, you must merge() both of the objects to maintain both sides of the relationship. Cascade merge is not enough as you have removed the objects, so there is nothing to cascade to.

You could also check the state of your objects after the merge, and before and after the commit.

Perhaps include your merge code.

James
  • 17,965
  • 11
  • 91
  • 146
  • Thanks, now I understand what the problem is! But what do you mean with "You could also check the state of your objects after the merge, and before and after the commit". My merge code is nothing more than em.merge(user). All the modifications to the user are done in the frontend (while the entity is detached). – Theo Jan 11 '11 at 22:19
0

the only explanation I can figure out, user object that you set empty list to its associatedSender field is not original cached object, it is just a copy ...

Gursel Koca
  • 20,940
  • 2
  • 24
  • 34
  • The user object is retrieved via `em.find()` and is not copied anywhere. Modifications to it are performed in the detached state (in a JSF managed bean). – Theo Jan 11 '11 at 07:51