JPA PessimisticLockScope.NORMAL and locking "relationships"
Entity relationships are mapped to database FK associations.
The PessimisticLockScope.NORMAL
will issue a quite aggressive database exclusive locking on:
- the entity dissociated table rows
- in a joined table inheritance structure both the base table and the subclass table are going to be locked
- all
@ManyToOne
and@OneToOne
associated table rows that have an actual FK relationship (e.g. the side with@JoinColumn
). But it means you can't alter the FK info, meaning you can't set it to null or to any other different value. So only the FK column value is locked not the other table associated FK row.
The @OneToMany
, @ManyToMany
and non-owning @OneToOne
and @ManyToOne
associations are not going to be locked because these associations have only an Object-Oriented equivalent and the locking happens solely at the database level.
The PessimisticLockScope.EXTENDED
will expand to the @OneToMany
and @ManyToMany
associations too. But again, this only applies to FK column values not to whole rows. So this locking will prevent adding/removing elements to/from @OneToMany
/@ManyToMany
associations. It doesn't prevent the contained elements from being updated. For that, you will have to lock each contained entity.
Here are few experiments regarding the question. I am using Hibernate 4.3.6 as JPA provider and MySQL 5.6 as database.
Few test entities - TestPerson , TestUser, TestOrder
TestUser extends TestPerson (with JOINED inheritance) and TestUser has a bidirectional OneToMany list of TestOrders
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class TestPerson {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
private String address;
//getters and setters
@Entity
public class TestUser extends TestPerson {
@OneToMany(fetch=FetchType.LAZY,mappedBy="user")
private List<TestOrder> orders ;
//getters and setters
@Entity
public class TestOrder {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
@ManyToOne
@JoinTable(name="test_user_orders")
private TestUser user;
private String orderNumber ;
//getters and setters**
Data creation code:
em.getTransaction().begin();
TestUser user = new TestUser();
user.setName("TestUser"+System.currentTimeMillis());
user.setAddress("TestUserAddress1");
em.persist(user);
List<TestOrder> orders = new ArrayList();
for (int i=1;i<6;i++){
TestOrder order = new TestOrder();
order.setOrderNumber("ON"+System.currentTimeMillis());
order.setUser(user);
em.persist(order);
orders.add(order);
}
user.setOrders(orders);
em.getTransaction().commit();
em.close();
mysql> select * from test_person;
+----+------------------+-----------------------+
| id | address | name |
+----+------------------+-----------------------+
| 1 | TestUserAddress1 | TestUser1406031063539 |
+----+------------------+-----------------------+
1 row in set (0.00 sec)
mysql> select * from test_user;
+----+
| id |
+----+
| 1 |
+----+
mysql> select * from test_order;
+----+-----------------+
| id | order_number |
+----+-----------------+
| 1 | ON1406031063627 |
| 2 | ON1406031063673 |
| 3 | ON1406031063678 |
| 4 | ON1406031063683 |
| 5 | ON1406031063686 |
+----+-----------------+
mysql> select * from test_user_orders;
+------+----+
| user | id |
+------+----+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 1 | 5 |
+------+----+
Now doing a lookup for MnayToOne side i.e, TestOrder
Map<String, Object> map = new HashMap<String, Object>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);
TestOrder order = em2.find(TestOrder.class, new Long(1), LockModeType.PESSIMISTIC_WRITE, map);
Note the "for update" in the query for pessimistic locking. This query has the join table included too.
select
testorder0_.id as id1_8_0_,
testorder0_.order_number as order_nu2_8_0_,
testorder0_1_.user as user1_11_0_
from
test_order testorder0_
left outer join
test_user_orders testorder0_1_
on testorder0_.id=testorder0_1_.id
where
testorder0_.id=? for update
Hibernate:
select
testuser0_.id as id1_9_0_,
testuser0_1_.address as address2_9_0_,
testuser0_1_.name as name3_9_0_
from
test_user testuser0_
inner join
test_person testuser0_1_
on testuser0_.id=testuser0_1_.id
where
testuser0_.id=?
Also when I query for the User, this time only the tables involved in user hierarchy are locked by "for update"
Map<String, Object> map = new HashMap<String, Object>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);
TestUser user = em2.find(TestUser.class, new Long(2), LockModeType.PESSIMISTIC_WRITE,map);
user.getOrders().size(); // to force initialization of orders
The resulting SQL is :
select
testuser0_.id as id1_9_0_,
testuser0_1_.address as address2_9_0_,
testuser0_1_.name as name3_9_0_
from
test_user testuser0_
inner join
test_person testuser0_1_
on testuser0_.id=testuser0_1_.id
where
testuser0_.id=? for update
Hibernate:
select
orders0_.user as user1_9_0_,
orders0_.id as id2_11_0_,
testorder1_.id as id1_8_1_,
testorder1_.order_number as order_nu2_8_1_,
testorder1_1_.user as user1_11_1_
from
test_user_orders orders0_
inner join
test_order testorder1_
on orders0_.id=testorder1_.id
left outer join
test_user_orders testorder1_1_
on testorder1_.id=testorder1_1_.id
where
orders0_.user=?