微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决批量UPDATE后的JPA JQL查询看到过时的值

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

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

我无法理解JPQL SELECT从何处获取过时的数据。它们显然不在共享缓存中(通过使用BYPASS提示和缓存接口的contains()方法进行确认)。

我做的实验:

有什么主意吗?

    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,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,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,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,main])--Execute query ReadAllQuery(referenceClass=Employee sql="SELECT ID,SALARY FROM EMPLOYEE")

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

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

[EL Finest]: connection: 2020-10-20 18:14:08.099--ServerSession(1006485584)--Connection(1482246673)--Thread(Thread[main,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,SALARY FROM EMPLOYEE")

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

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

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

[EL Finest]: transaction: 2020-10-20 18:14:08.122--UnitOfWork(955611965)--Thread(Thread[main,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,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,main])--Merge clone Employee id: 3 name: Mario salary: 2400 

800
1600
2400

解决方法

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。