Reputation: 1580
I'm having two JPA entities which are linked to each other. A person can be member of zero or one groups. A group can have zero or more members.
@Entity
public class PersonEntity {
@ManyToOne
@JoinColumn(name = "GROUP_ID")
private GroupEntity group;
}
@Entity
public class GroupEntity {
@OneToMany(mappedBy = "group", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH })
private Collection<PersonEntity> persons;
}
The problem I'm having is that multiple threads can remove/add persons to a group. So when two threads remove a person from the same group, each of them is unaware of what happened in the other thread.
I know I can wrap this all in a synchronized block, but that would not help me if the application is running on multiple machines that share a database.
How could JPA be aware that the collection on GroupEntity has been changed by another thread?
Upvotes: 0
Views: 1118
Reputation: 886
You are right about the synchronized block: that will not apply across JVMs
Use a database locking strategy to handle concurrent updates to an entity
Instead, you want to consider a database locking strategy and using database transactions. JPA supports both optimistic and pessimistic locking. You can typically achieve better performance with optimistic locking which uses a version field to track updates to each of your entities.
In your scenario, if the threads each perform their remove and update in operations in one database with optimistic locking, one thread will succeed and the other will throw a locking exception because it tried to update an entity which has been removed
Use a join table to eliminate contention between modifications to Group membership and Person attributes
Assuming that removing the Person from the group does not delete the person, there may be another way to remove contention in this scenario. Typically, in a one-to-many relationship, the Person row will hold a reference to the group ID to which it belongs. This causes Thread 1 and Thread 2 to compete for updates to a Person row. Instead, you could move the one-to-many relationship to a separate join table so that Thread 1 can remove PersonA from Group1 at the same time that Thread2 does post processing with PersonA
On synchronizing relationship collections between JVMs
JPA implements several persistence patterns Martin Fowler details in Patterns of Enterprise Application Architecture. One of these patterns is the Unit of Work which tracks changes made during a "session". I believe Thread1 and Thread2 are working in separate units of work and therefore they should avoid trying to synchronize their in-memory caches of data retrieved from the database. Instead, Thread1 will get Thread2's changes the next time it queries the database. Let the database own the concern of synchronizing state across the distributed system.
If you need to manage distributed transactions, look into JTA; however, I'm not sure which JPA providers are able to distribute their unit of work's in-memory state within a distributed transaction. This is situation I would try to avoid instead of handle
Upvotes: 1