我们在 Web 应用程序中使用 JPA 的 EclipseLink 实现。我们在无状态 bean 中使用具有事务作用域(JTA 事务类型)的 EntityManager。

根据我从多个来源发现的内容以及在与我的大学讨论后,这是符合 entitymanager 范围的推荐方式。但是从我个人在开发过程中的实验,我发现了几个问题。例如,如果您在一个方法中调用 entityManager.find(class, id) 两次,它将返回两个不同的对象,这是预期会发生的情况,因为实体管理器是事务范围,并且我们有两个单独的事务。所以这种方式使用更多的内存。

我的问题是,为什么我们不应该在有状态 bean 中使用具有扩展范围的实体管理器。没有任何优势吗?它会导致什么问题?

我们使用什么:

@Stateless
@LocalBean
public class SessionBean{
    @PersistanceContext(unitName = "name", type = PersistenceContextType.TRANSACTION)
    EntityManager entityManager;
}

我想更改为:

@Stateful
@LocalBean
public class SessionBean{
    @PersistanceContext(unitName = "name", type = PersistenceContextType.EXTENDED)
    EntityManager entityManager;
}

相反!通常,事务范围的实体管理器往往比扩展实体管理器的内存效率更高。

首先,让我们说出本次讨论中的主要概念:持久性上下文。

根据规范:“持久性上下文是一组实体实例,其中对于任何持久性实体标识都有一个唯一的实体实例”。

了解持久性上下文与 JTA 事务交互的方式是 JPA 程序员的基本知识。在这两个提议的场景中,我们都在谈论容器管理的持久性上下文。那么它们是如何工作的呢?

事务范围的持久化上下文

顾名思义,事务范围的持久性上下文绑定到事务及其生命周期。它在事务中创建,并在事务结束时关闭。而且,在我们的上下文中最重要的是,它是为事务创建的唯一持久性上下文。EntityManager这意味着即使在事务期间创建了多个实例,它们也会共享同一个持久化上下文。

这意味着注入EntityManager接口的不同服务 bean 共享同一组实体(相同的持久性上下文),尽管EntityManager分配给它们的实例不同。因此调用entityManager.find(class, id)两次、三次或更多次,会导致返回同一个实例。find()即使方法调用是从不同的实例进行的,也是如此EntityManager只要应用程序运行相同的事务,就会返回相同的实体实例——无论EntityManager它是从哪个实体检索的。

最后,如前所述,绑定到事务意味着持久性上下文在事务关闭时关闭。这意味着持久性上下文首先被刷新,然后在事务结束后被清除。与扩展持久性上下文相比,此行为代表了内存分配的巨大收益这是因为一旦持久性上下文被清除,事务期间检索到的实体实例就会被垃圾回收。

让我们在示例中查看共享持久性上下文。

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    /* constructors, getters and setters omitted */
}

@Stateless
public class StatelessBeanA {

    @PersistenceContext
    private EntityManager em;

    public void changePersonName(long id, String newName) {
        Person person = em.find(Person.class, id);
        person.setName(newName);
    }

    public Person getPerson(long id) {
        return em.find(Person.class, id);
    }

    public EntityManager getEm() {
        return em;
    }
}

@Stateless
public class StatelessBeanB {

    @PersistenceContext
    private EntityManager em;

    public void printsPersonName(long id) {
        var person = em.find(Person.class, id);
        System.out.println(person.getName());
    }

    public Person getPerson(long id) {
        return em.find(Person.class, id);
    }

    public EntityManager getEm() {
        return em;
    }
}

@Stateless
public class Controller {

    @PersistenceContext
    EntityManager em;
    @EJB
    StatelessBeanA serviceA;
    @EJB
    StatelessBeanB serviceB;

    public void execute() {
        // prints 'false'
        System.out.println(Objects.equals(serviceA.getEm(), serviceB.getEm()));

        Person person = new Person("oldName");
        em.persist(person);
        final long id = person.getId();

        serviceA.changePersonName(id, "newName");

        // prints 'newName'
        serviceB.printsPersonName(id);

        var p1 = serviceA.getPerson(id);
        var p2 = serviceB.getPerson(id);

        // prints 'true'
        System.out.println(Objects.equals(p1, p2));
    }
}

扩展持久性上下文

再次引用规范

"A container-managed extended persistence context can only be initiated within the scope of a stateful session bean. It exists from the point at which the stateful session bean that declares a dependency on an entity manager of type PersistenceContextType.EXTENDED is created, and is said to be bound to the stateful session bean." ... "The persistence context is closed by the container when the @Remove method of the stateful session bean completes (or the stateful session bean instance is otherwise destroyed)."

A major disadvantage of indiscriminately using extended persistence contexts is the fact the persistence context will exist for the entire lifecycle of the stateful session bean. In other words, the entity instances stored in the persistence context will not be dropped until the bean is dropped.

Another disadvantage is the complex transaction flow to be observed during its use. Imagine a situation where a stateful session bean is called from a stateless session bean. Imagine also the stateless bean, before calling the stateful bean, used a transaction-scoped entity manager. This will create a transaction-scoped persistence context for the entity manager in the stateless bean. Imagine now the stateful bean uses an extended entity manager. When the stateless session bean calls the stateful session bean, an exception is thrown. This is because when the container tries to assign the extended persistence context to the current transaction, there is already a persistence context assigned to the transaction.


So back to your question. If I understand correctly, the main reason you want to use extended persistence contexts is their ability to keep retrieved entity instances in memory. Well, despite being memory-inefficient (many unused entities are kept in memory), it could be argued that at least it is faster. However, even this advantage can be questioned, since the persistence providers tend to implement different layers of cache.

At last, I'd like to leave the chapter 6 of the book Pro JPA 2 in Java EE 8 as a great reference on the subject.


也许更改 EntityManager 的范围不是最好的方法。我考虑对您的数据库结果进行缓存,通过此实现,JPA 将在缓存中返回一个对象。

好处与缺点相同。通过使用扩展范围,您可以获得不必查找(或仅克隆,因为 EclipseLink 具有二级/工厂级缓存)已经从数据库中第二次读入的实例的性能。但是您让 EntityManager 管理这些实例的时间更长,并且 EM 在加入另一个事务时检查它们的所有更改时可能会有一些额外的开销。虽然更改跟踪和其他优化可以大大降低这些成本 - 但要真正知道您是否会从更改中受益,您必须测试这两个选项。