JPA实体管理器事务作用域与扩展作用域

db2dz4w8  于 2023-03-13  发布在  其他
关注(0)|答案(1)|浏览(119)

我们在Web应用程序中使用EclipseLink实现JPA。我们在一个Stateless 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;
}
wwtsj6pe

wwtsj6pe1#

相反!一般来说,事务作用域的实体管理器往往比扩展实体管理器更有内存效率。
首先,让我们为讨论中的主要概念命名:持久性上下文。
根据质量标准:“* 持久性上下文是一组实体示例,其中任何持久性实体标识都有一个唯一的实体示例 *"。
理解持久性上下文与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));
    }
}

扩展持久性上下文

再次引用规格:

  • “容器管理的扩展持久性上下文只能在有状态会话bean的范围内启动。它从声明对PersistenceContextType.EXTENDED类型的实体管理器的依赖性的有状态会话bean创建时开始存在,并且被称为绑定到有状态会话Bean。”...“当@有状态会话Bean的Remove方法完成(或有状态会话Bean示例被销毁)。"*

不加选择地使用 * 扩展持久性上下文 * 的一个主要缺点是持久性上下文将在有状态会话bean的整个生命周期中存在。换句话说,在持久性上下文中存储的实体示例将不会被删除,直到bean被删除。
另一个缺点是在使用过程中要观察到复杂的事务流。想象一下从无状态会话bean调用有状态会话bean的情况。再想象一下无状态bean,在调用有状态bean之前,使用了一个 * 事务作用域实体管理器 *。这将为无状态bean中的实体管理器创建一个 * 事务作用域持久性上下文 *。现在假设有状态bean使用了一个 *扩展实体管理器 *。当无状态会话bean调用有状态会话bean时,会抛出异常。这是因为当容器尝试将 * 扩展持久性上下文 * 分配给当前事务时,已经有持久性上下文分配给该事务。
回到你的问题上,如果我理解正确的话,你想使用 * 扩展持久性上下文 * 的主要原因是它们能够将检索到的实体示例保存在内存中。(许多未使用的实体被保存在内存中),可以争辩说至少它更快。然而,即使是这种优势也可以受到质疑,因为持久性提供者倾向于实现不同的高速缓存层。
最后,我想把书 * Java EE 8中的Pro JPA 2 * 的第6章作为这个主题的一个很好的参考。

相关问题