容器管理事务中 CRUD 操作中实体的行为

Behaviour of entities in CRUD operations in container-managed transactions

该场景基于完整配置文件 Java EE 7 (Wildfly 8) 并使用 EJB 3.2、JPA 2.1、JSF 2.2(Mojarra、Primefaces 库)和以下项目结构:

基本的 CRUD 场景如下所示:

项目-overview.xhtml

        <h:form id="itemPanel">
        <p:panelGrid id="itemPanelGrid" columns="2">
            <f:facet name="header">Item</f:facet>

            <h:outputLabel for="itemName" value="Item name: " />
            <p:inputText id="itemName" value="#{itemController.item.itemName}"></p:inputText>

            <f:facet name="footer">
                <p:commandButton id="newItem" value="New" update="itemPanelGrid"
                    process="@this" actionListener="#{itemController.newItem}">
                    <p:resetInput target="itemPanelGrid" />
                </p:commandButton>
                <p:commandButton id="deleteItem" value="Delete"
                    update="itemsPanel @form"
                    actionListener="#{itemController.deleteItem}" />
                <p:commandButton id="saveItem" value="Save"
                    update="itemsPanel @form" action="#{itemController.saveItem}" />
            </f:facet>
        </p:panelGrid>
        <p:panel id="itemsPanel">
            <p:dataTable id="itemTable" var="tableItem" selectionMode="single"
                selection="#{itemController.selectedItem}" rowKey="#{tableItem.id}"
                emptyMessage="No items available" value="#{itemController.items}">
                <p:ajax event="rowSelect" listener="#{itemController.onRowSelect}"
                    update="@this @form" />
                <p:column headerText="Id">
                    <h:outputText value="#{tableItem.id}" />
                </p:column>
                <p:column headerText="Item name">
                    <h:outputText value="#{tableItem.itemName}" />
                </p:column>
            </p:dataTable>
        </p:panel>
    </h:form>

ItemController.java

public class ItemController implements Serializable {

@EJB
private ItemServiceBean itemServiceBean;
private Item item;
private Item selectedItem;
private List<Item> items;

@PostConstruct
public void init() {
    item = new Item();
    items = itemServiceBean.getAllItems();
}

public void deleteItem() {
    if (items.contains(item))
        items.remove(item);

    itemServiceBean.deleteItem(item);
}

public String saveItem() {
    if (!items.contains(item))
        items.add(item);

    itemServiceBean.saveItem(item);
    return null;
}

public void newItem() {
    item = new Item();
}

public List<Item> getItems() {
    return items;
}

public void setItems(List<Item> items) {
    this.items = items;
}

public Item getItem() {
    return item;
}

public void setItem(Item item) {
    this.item = item;
}

public Item getSelectedItem() {
    return selectedItem;
}

public void setSelectedItem(Item selectedItem) {
    this.selectedItem = selectedItem;
}

public void onRowSelect(SelectEvent event) {
    FacesMessage msg = new FacesMessage("Item selected", ((Item) event.getObject()).getId() + "");
    FacesContext.getCurrentInstance().addMessage(null, msg);
    this.item = this.selectedItem;
}

}

ItemServiceBean.java

public class ItemServiceBean {

@PersistenceContext
private EntityManager entityManager;

public ItemServiceBean() { }

public void saveItem(Item item) { 
    entityManager.persist(item);
}

public void deleteItem(Item item) { 
    Item managedItem = getItem(item);
    entityManager.remove(managedItem);
}

public Item updateItem(Item item) { 
    Item managedItem = getItem(item);
    return entityManager.merge(managedItem);
}

public List<Item> getAllItems() { 
    TypedQuery<Item> query = entityManager.createQuery("SELECT i FROM Item i", Item.class);
    return query.getResultList();    
}

public Item getItem(Item item) { 
    return entityManager.find(Item.class, item.getId());
}

}

支持 bean 是@ViewScoped。后面的 EJB 是无状态的,我想它是一个容器管理的 bean(因为它是默认设置)。

如果您尝试使用 EntityManagers remove 方法删除项目并简单地传递当前选定的项目,则对象将被分离。所以,我所做的是从数据库中检索项目,将其放入持久性上下文并将其删除。我原以为它会有点不同,不需要从数据库中检索对象,再次将其放入上下文并删除它。是否有可能避免这些额外的步骤?

我曾多次尝试更新现有项目。如果您询问持久性上下文是否已使用 EntityManagers contains 方法管理修改的项目,结果表明该项目不在持久性上下文中。因此,我再次需要从数据库中检索对象,然后使用 merge 更新它。我真的怀疑这是首选方式,如果有人对此有任何提示,我会很高兴。

我假设整个事务保持 "open" 并且 object/s remain/s 附加到持久性上下文。

谢谢。

If you try to delete an item using EntityManagers remove method and simply pass the currently selected item, the object is detached. So, what I did is retrieving the item from database, putting it into the persistence context and removing it. I had expected it to be a little different without needing to retrieve the object from the database, putting it into the context again and removing it. Is there a possibility to avoid these additional steps?

没有。 EntityManager#remove() 需要托管实体。 IE。它必须在同一笔交易中获得。否则,当多个用户同时编辑甚至删除实体时,这将 运行 陷入困境,包括所有需要级联的 FK 关系上的实体,如果有的话。规范的方式是:

public void deleteItem(Item item) {
    entityManager.remove(entityManager.contains(item) ? item : entityManager.merge(item));
}

I have had several tries on updating an already existing item. If you ask the persistence context if the modified item is already managed using the EntityManagers contains method it turns out that the item is not in the persistence context. So, once again, I needed to retrieve the object from the database and update it using merge afterwards. I really doubt that this is the preferred way and would be glad if someone has any hints on this.

我不确定您的具体问题是什么。这应该可以工作。如果您从 DB 获取该项目然后合并它,就像您目前所做的那样,那么您将错过所有更改的值。实际上,您正在执行空操作。以下应该可以正常工作:

public Item updateItem(Item item) { 
    return entityManager.merge(item);
}

I was assuming that the whole transaction remains "open" and the object/s remain/s attached to the persistence context.

没有。在 @Stateless EJB 的情况下,只要 EJB 的客户端(在您的情况下是 JSF 托管 bean)对其进行单个方法调用,事务就会持续。请注意,默认情况下,无论 EJB 方法中的任何嵌套 EJB 方法调用如何,它们都将 运行 在同一事务中。因此,一旦顶层 @Stateless EJB 方法调用 returns,事务将被提交并且实体变得分离。否则,当多个用户同时编辑同一个实体时,您可能 运行 进入 OptimisticLockException 的所有地方。

另请参阅:

  • When is it necessary or convenient to use Spring or EJB3 or all of them together?