13

I have 2 entities with many-to-many relationship. The Movie Entity is the owner of this relation, so when I want to delete an Actor Entity I use a method annotated @PreRemove to delete any occurrences of Actor ID in Movie cast to avoid "Foreign key violation exception".

Movie class

@Entity
public class Movie extends AbstractBusinessObject{

    @ManyToMany
    private Map<String, Actor> cast;

    // setters and getters

    public void removeCastMember(Actor actor){

        for (Entry<String, Actor> e : cast.entrySet()) {
            if(e.getValue().id.equals(actor.id)){
                cast.remove(e.getKey());
            }
        }

    } // removeCastMember()

}

Actor class

@Entity
public class Actor extends AbstractBusinessObject{

    @ManyToMany(mappedBy = "cast")
    private Set<Movie> movies;

    // setters and getters

    @PreRemove
    private void removeActorFromMovies() {
        for (Movie m : movies) {
            m.removeCastMember(this);
        }
    }

}

To be clear, from my testing, it works - movie objects are correctly updated in the database. However, I cannot understand how is it possible when there are no calls to saveOrUpdate() or persist/merge those objects.

Kuba Spatny
  • 26,618
  • 9
  • 40
  • 63

1 Answers1

19

That's a fundamental feature of JPA/Hibernate. All the changes made to attached entities are automatically made persistent: Hibernate manages them, so it compares their current state with their initial state, and automatically makes all the changes persistent.

This is extremely useful, because you don't have to track all the entities that have been modified in a complex business method modifying lots of entities. And it's also efficient because Hibernate won't execute unnecessary SQL: if an entity hasn't changed during the transaction, no SQL update query will be executed for this entity. And if you modify entities and then throw an exception rollbacking the transaction, Hibernate will skip the updates.

So, typical JPA code would look like this:

void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
    Account from = em.find(Account.class, fromAccountId); // from is managed by JPA
    Account to = em.find(Account.class, ftoAccountId); // to is managed by JPA
    from.remove(amount);
    to.add(amount);

    // now the transaction ends, Hibernate sees that the state of from and to 
    // has changed, and it saves the entities automatically before the commit
}

persist() is used to make a new entity persistent, i.e. to make it managed by Hibernate.

merge() is used to take a detached entity (i.e. an entity which is not managed by Hibernate, but already has an ID and a state) and to copy its state to the attached entity having the same ID.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thank you for such thorough answer. I really forgot about the important part: `merge is used to take a detached entity`. – Kuba Spatny Nov 03 '13 at 11:00