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=?