0

In an JSF (Primefaces) application I have the following entity:

@Entity
@Table(name = "shop_tree")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "itemType", discriminatorType =     DiscriminatorType.STRING)
@NamedQuery(name = "selectSorted", query = "SELECT t FROM ShopTree t     ORDER BY t.parentId ASC, t.position ASC")
public abstract class ShopTree implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @NotNull
  private Long parentId;
  @NotNull
  private Integer position = 0;
  @NotNull
  private String name;
  private String seoLinkName;
  @Enumerated(EnumType.STRING)
  @Column(name = "itemType", insertable = false, updatable = false)
  @NotNull
  private TreeItemType itemType = TreeItemType.SPACER;
  private boolean released;
  private int importError;
  private boolean seoRelevant;
  @Version
  @Column(name = "version", nullable = false)
  private long version = 0l;

And these two methods in the corresponding facade class (a @Stateless bean):

public T merge(T entity) throws ConcurrentUpdateException {
    try {
      return getEntityManager().merge(entity);
    } catch (OptimisticLockException ex) {
      log.warn("OptimisticLocking Exception: ex.getEntity() {} -     entity {}", ex.getEntity().getClass().getName(), entity);
      throw new ConcurrentUpdateException(entity);
    }
  }

  public void remove(T entity) {
    getEntityManager().remove(getEntityManager().merge(entity));
  }

There are about 6 special types that inherit from this base class, all of them just contain 1 or 2 more basic attributes like additional int, long or string - really nothing special.

Example:

@Entity
@DiscriminatorValue(TreeItemType.Values.FREE_LINK)
public class Freelink extends ShopTree {

  private String freeLink;

  public Freelink() {
    super();
    this.setItemType(TreeItemType.FREE_LINK);
  }

  // Getter /Setter
}

Since the entities form a somewhat complex tree structure I read them into memory (via the named query) and keep them in session. Now when a user in one session deletes an item and another user edits one of the deleted entities, the entity is simply stored in database again - without any exception. It gets a new id just like I used persist instead of merge.

How can that be? Shouldn't there be an exception?

All deletion and merge is done via the above methods, there is absolutely no exception.

Is there any way I prevent jpa from saving the removed entity to database again. I would like to be informed by JPA that the entity I just try to

I am using

  • wildfly 8.2.0-final (which means hibernate 4.3.1 I think and
  • java 8
  • Java ee 7 api
  • JSF 2.2
Lasrik
  • 589
  • 1
  • 8
  • 22
  • What are you using to delineate transactions? The `OptimisticLockException` will not be thrown until the transaction commits – Steve C Jul 01 '15 at 13:38
  • Are the users sharing the same 'session' or does each user have their own 'session'? If it is the latter then the Entities much be detached, which means that each copy of the Entity is a separate instance. Does the merged Entity (saved by the second user) have an Id when it is saved? Also, why do you initialise `version`? I have never seen that before and it looks wrong. We've never done that in any of the 400-odd Entities that we use. Usually the underlying persistence provider takes care of initialising the version field. – DuncanKinnear Jul 01 '15 at 22:15
  • @SteveC nothing special, the container manages transactions for me. I do not have any explicit transaction attributes or annotation on any method or class. – Lasrik Jul 02 '15 at 07:42
  • @DuncanKinnear sessions are completely container managed. I just have a @ SessionScoped TreeViewBean which constructs the tree in a @ PostConstruct method. So I assume every user has its own. All entities are detached automatically, that's why I have the merge in the remove method. Otherwise it would throw an exception. Yes the entity has an id when I merge - as I wrote it gets a new one upon merge just as if I had used persist rather than merge. – Lasrik Jul 02 '15 at 07:46
  • @SteveC the `OptimisticLockingException` is thrown reliably, I tested that. – Lasrik Jul 02 '15 at 07:48
  • Initializing the version field is taken from here: http://stackoverflow.com/a/26731878/1075733 – Lasrik Jul 02 '15 at 07:50

1 Answers1

0

If you use the merge method without any existence check you are assuming that you are the only one tha hold the data in that moment, the merge do a session check, but the entity isn't in session because is a form post, so it try to fetch from database, but is already deleted (the delete is committed) sot it mark the object as new (then the commit create the insert statement).

If, by design, you know that other users can modify or delete the same entities you need to do a manual existence check before the merge, or use a lock behavior to block concurrent edits of same objects.

fantarama
  • 862
  • 6
  • 14