提问者:小点点

批量更新后的 JPA JQL 查询看到过时的值


我有一个EclipseLink JPA演示应用程序,它在执行一个JPQL UPDATE语句后发出一个JPQL SELECT语句。SELECT语句“看到”陈旧数据,如下所示:

  • 如果未提供提示,则结果数据已过时
  • 如果使用 .setHint(“javax.persistence.cache.storeMode”, “REFRESH”),则检索更新的数据
  • 如果检索 .setHint(“javax.persistence.cache.retrieveMode”, “BYPASS”) 过时数据
  • 如果我在查询之前执行 em.clear(),查询将检索更新的值(但这很明显,我想知道当持久性上下文未清除时会发生什么)。但是,这指向 1 级缓存问题。

我不明白 JPQL SELECT 从哪里获取过时的数据。它们显然不在共享缓存中(通过使用缓存接口的 BYPASS 提示和 contains() 方法确认)。

我做的实验:

    < li >将where 1=1添加到陈旧检索查询中,以检查是否发生了某些查询代码缓存(无变化) < li >从第二个SELECT查询中删除提示,以检查可能延迟的数据库提交是否存在时间问题(第二个查询也会发现陈旧数据)。

知道吗?

    package examples.client;
    
    import examples.model.Employee;
    import java.util.List;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    public class EmployeeUPDATEModification {
    
        public static void main(String[] args) {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
            EntityManager em = emf.createEntityManager();
    
            System.out.println("***INITIAL SALARY VALUES\n");
            List<Employee> initial = em.createQuery("SELECT e FROM Employee e", Employee.class).getResultList();
            for (Employee e : initial) {
                System.out.println(e.getSalary());
            }
            System.out.println("***TESTING BULK UPDATE\n");
            em.getTransaction().begin();
            em.createQuery("UPDATE Employee e SET e.salary = e.salary*2").executeUpdate();
            em.getTransaction().commit();
    
            System.out.println("***SALARY VALUES AFTER BULK UPDATE FROM INITIAL LIST\n");
            for (Employee e : initial) {
                System.out.println(e.getSalary());
            }
    
            System.out.println("\n***PRINTING THE SALARY FROM A QUERY WITHOUT HINT \n");
            List<Employee> result = em.createQuery("SELECT e FROM Employee e", Employee.class).getResultList();
            for (Employee e : result) {
                System.out.println(e.getSalary());
            }
    
            System.out.println("\n***CHECKING THE SHARED CACHE\n");
            for (Employee e : result) {
                System.out.println(emf.getCache().contains(Employee.class, e.getId()) ? e + " is in shared chache"
                        : e + " is NOT in shared cache");
            }
    
            System.out.println("\n***PRINTING THE SALARY FROM A QUERY WITH HINT \n");
            List<Employee> result3 = em.createQuery("SELECT e FROM Employee e", Employee.class)
                    .setHint("javax.persistence.cache.storeMode", "REFRESH").getResultList();
            for (Employee e : result3) {
                System.out.println(e.getSalary());
            }
    
            // close the EM and EMF when done
            em.close();
            emf.close();
    
        }
    }

控制台

***INITIAL SALARY VALUES

[EL Fine]: sql: 2020-10-20 17:21:25.213--ServerSession(2092769598)--Connection(110651474)--Thread(Thread[main,5,main])--SELECT ID, NAME, SALARY FROM EMPLOYEE

100
200
300

***TESTING BULK UPDATE

[EL Fine]: sql: 2020-10-20 17:21:25.241--ClientSession(706665172)--Connection(110651474)--Thread(Thread[main,5,main])--UPDATE EMPLOYEE SET SALARY = (SALARY * ?)
    bind => [2]

***SALARY VALUES AFTER BULK UPDATE FROM INITIAL LIST

100
200
300

***PRINTING THE SALARY FROM A QUERY WITHOUT HINT 

[EL Fine]: sql: 2020-10-20 17:21:25.256--ServerSession(2092769598)--Connection(110651474)--Thread(Thread[main,5,main])--SELECT ID, NAME, SALARY FROM EMPLOYEE

100
200
300

***CHECKING THE SHARED CACHE

Employee id: 1 name: Piero salary: 100 is NOT in shared cache

Employee id: 2 name: Aldo salary: 200 is NOT in shared cache

Employee id: 3 name: Mario salary: 300 is NOT in shared cache

***PRINTING THE SALARY FROM A QUERY WITH HINT 

[EL Fine]: sql: 2020-10-20 17:21:25.271--ServerSession(2092769598)--Connection(110651474)--Thread(Thread[main,5,main])--SELECT ID, NAME, SALARY FROM EMPLOYEE

200
400
600

具有最佳日志记录级别的控制台

***PRINTING THE SALARY FROM A QUERY WITHOUT HINT 

[EL Finest]: query: 2020-10-20 18:14:08.097--UnitOfWork(955611965)--Thread(Thread[main,5,main])--Execute query ReadAllQuery(referenceClass=Employee sql="SELECT ID, NAME, SALARY FROM EMPLOYEE")

[EL Finest]: connection: 2020-10-20 18:14:08.097--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--Connection acquired from connection pool [default].

[EL Fine]: sql: 2020-10-20 18:14:08.097--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--SELECT ID, NAME, SALARY FROM EMPLOYEE

[EL Finest]: connection: 2020-10-20 18:14:08.099--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--Connection released to connection pool [default].

400
800
1200

PRINTING THE SALARY FROM A QUERY WITH HINT 

***

[EL Finest]: query: 2020-10-20 18:14:08.119--UnitOfWork(955611965)--Thread(Thread[main,5,main])--Execute query ReadAllQuery(referenceClass=Employee sql="SELECT ID, NAME, SALARY FROM EMPLOYEE")

[EL Finest]: connection: 2020-10-20 18:14:08.119--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--Connection acquired from connection pool [default].

[EL Fine]: sql: 2020-10-20 18:14:08.119--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--SELECT ID, NAME, SALARY FROM EMPLOYEE

[EL Finest]: connection: 2020-10-20 18:14:08.121--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,5,main])--Connection released to connection pool [default].

[EL Finest]: transaction: 2020-10-20 18:14:08.122--UnitOfWork(955611965)--Thread(Thread[main,5,main])--Merge clone Employee id: 1 name: Piero salary: 800 

[EL Finest]: transaction: 2020-10-20 18:14:08.122--UnitOfWork(955611965)--Thread(Thread[main,5,main])--Merge clone Employee id: 2 name: Aldo salary: 1600 

[EL Finest]: transaction: 2020-10-20 18:14:08.123--UnitOfWork(955611965)--Thread(Thread[main,5,main])--Merge clone Employee id: 3 name: Mario salary: 2400 

800
1600
2400

共1个答案

匿名用户

您发布的答案提到了您的问题——您有一个从本地上下文读取的实体,并且由于模型对象之外的更新(您的批量更新查询)而使其过时。每次您从该EntityManager读取时,您都会得到相同的过时员工实例数据——JPA要求它维护对象身份,并且由于您可能在其中有未提交的更改,因此不能将它们清除。因此它将执行完整列表操作,但当它看到它已经缓存/管理的Emp id时,只需按原样返回该实例。

JPA规范指出,批量更新和删除会以上下文中可能看不到的方式更改内容:第4.10节:“持久性上下文与大容量更新或删除的结果不同步。执行大容量更新和删除操作时应小心,因为这些操作可能会导致数据库与活动持久性上下文中的实体之间不一致。通常,大批量更新和删除操作应仅在新持久性上下文的事务中执行或者在获取或访问其状态可能受到此类操作影响的实体之前。"

您可以通过强制刷新以后的查询、清除实体管理器或在提交事务后获取新的实体管理器来解决此问题。然后,所有读取都将使用数据库中的数据。