1

The scenario is based on the full-profile Java EE 7 (Wildfly 8) and uses EJB 3.2, JPA 2.1, JSF 2.2 (Mojarra, Primefaces library) and the following project structure:

  • Web project
  • EAR project
  • EJB project
  • JPA project

The basic CRUD scenario looks as follows:

  • User enters the page or clicks New, enters item name and clicks Save > item is saved in database and added to table
  • User selects item from table > item name field is updated with appropriate value from currently selected item
  • User selects item from table, modifies it and clicks save > modified item is updated in database and table
  • User selects item from table and clicks Delete > item is removed from database and table

item-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());
}

}

The backing bean is @ViewScoped. The EJB behind is stateless and I suppose it is a container-managed bean (as it is the default setting).

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?

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.

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

Thank you.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
fatdevelops
  • 435
  • 2
  • 6
  • 17

1 Answers1

1

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?

Nope. The EntityManager#remove() requires a managed entity. I.e. it must be obtained in the same transaction. This will otherwise run into trouble when multiple users concurrently edit or even remove the entity, including those on all FK relationships requiring a cascade, if any. The canonical way is:

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.

I'm not sure what your concrete problem is here. This should just work. If you obtain the item from DB and then merge it, as you currently do, then you'll miss all changed values. In effects, you're doing a no-op. The following ought to just work:

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.

Nope. The transaction lasts in case of a @Stateless EJB as long as a single method call on it by the EJB's client (in your case, the JSF managed bean). Note that this is by default regardless of any nested EJB method calls in the EJB method, they will all run in the same transaction. So, once the top level @Stateless EJB method call returns, the transaction will be committed and the entity becomes detached. You may otherwise run into OptimisticLockExceptions over all place when multiple users concurrently edit the same entity.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for clearing this up for me. Well, it took me quite some time to figure all that out and reconstruct. I can confirm that `entityManager.merge(item);` simply works as described. Fetching the entity from the database and merging it afterwards was indeed a useless (no-)operation. – fatdevelops Jan 08 '15 at 18:32