6

I thought that an entity found by em.find was automatically managed by em, even out a transaction, but this class below seems to show the contrary. Was I wrong or what is the mistake in that class?

@Stateful
@TransactionAttribute(NOT_SUPPORTED)
public class CustomerGateway {

  @PersistenceContext(unitName = "customersPU", type = EXTENDED)
  private EntityManager em;
  private Customer customer;

  public Customer find(Long id) {
    // customer is not managed!
    this.customer = em.find(Customer.class, id);
    // Print false!
    System.out.println("Method find: " + em.contains(customer));
    // Print false too (2 is the id of an entity)!
    System.out.println("Method find: " + em.contains(em.find(Customer.class, 2L));
    // A workaround
    customer = em.merge(customer);
    // Print true.
    System.out.println("Method find after merge: " + em.contains(customer));
    return this.customer;
  }

EDIT 1: code of the entity

@Entity
@NamedQuery(name = "Customer.all", query = "select c from Customer c")
public class Customer implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String name;

  public Customer() {
  }

  public Customer(String name) {
    this.name = name;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public int hashCode() {
    int hash = 0;
    hash += (id != null ? id.hashCode() : 0);
    return hash;
  }

  @Override
  public boolean equals(Object object) {
    // TODO: Warning - this method won't work in the case the id fields are not set
    if (!(object instanceof Customer)) {
      return false;
    }
    Customer other = (Customer) object;
    if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    return "entity.Customer[ id=" + id + " ]";
  }

}

Code of the stateful EJB:

@Stateful
@TransactionAttribute(NOT_SUPPORTED)
public class CustomerGateway {

  @PersistenceContext(type = PersistenceContextType.EXTENDED)
  private EntityManager em;

  private Customer customer;

  public Customer getCustomer() {
    return customer;
  }

  public void create(Customer customer) {
    em.persist(customer);
    this.customer = customer;
  }

  public Customer find(Long id) {
    this.customer = em.find(Customer.class, id);
    System.out.println("customer managed ? " + em.contains(this.customer));
    // Workaround :
//    this.customer = em.merge(customer);
    return customer;
  }

  public void remove(Long id) {
    Customer cust = em.getReference(Customer.class, id);
    em.remove(cust);
  }

  @TransactionAttribute(REQUIRES_NEW)
  public void save() {
  }

  public List<Customer> findAllCustomers() {
    Query query = em.createNamedQuery("Customer.all");
    return query.getResultList();
  }

  @Remove
  public void close() {
  }

}

I work with NetBeans 7.4, GlassFish 4.0, EJB 3.2, Java DB.

user1643352
  • 2,635
  • 2
  • 19
  • 25
  • Can't tell you why, but the `customer` instance must be detached in those first two calls. Have you tried changing `NOT_SUPPORTED` to `SUPPORTS` or `REQUIRED` and seeing if the behavior changes? – Durandal Jan 25 '14 at 22:51
  • 1
    +1 to the question. I found the following description in 'Mastering the Java Persistence API' book: "The find() operation returns a managed entity instance in all cases except when invoked outside of a transaction on a transaction-scoped entity manager. In this case, the entity instance is returned in a detached state." so the above description seems to be contradictory to the class in which find() is invoked outside of a transaction on an extended entity manager ... or we are missing something here (?) – wypieprz Jan 25 '14 at 23:11
  • @MagicMan: If I add @TransactionAttribute to the method find, the entity is managed. But, according to wypieprz, even without a transaction, the entity should be managed. – user1643352 Jan 25 '14 at 23:25
  • 1
    @user1643352 Could you post `Customer` entity source code? – Zaw Than oo Jan 27 '14 at 09:01
  • @CycDemo: what are you thinking of that could explain that the customer retrieved by find is not managed? Sorry but I don't know how to write the code of a class in a comment :-( – user1643352 Jan 31 '14 at 20:40
  • @CycDemo: I have edited my question with the code of the entity. – user1643352 Feb 01 '14 at 17:58

2 Answers2

2

All that you have experienced is according to the spec. The persistence context remains (and the entities keeps attached) while the transaction exists. So, in a extended persistence context and a NOT_SUPPORTED transaction the objects retrieved by calling find method are dettached. -Also, if your Customer object has lazy relationships and you try to access them, then, it is highly probable that you will get a runtime exception.-

Now, why the merge method is just ok?. Well, first remember that merge returns a managed entity and is attaching the customer to the persistence context.

Second, you have an EXTENDED persistence context, so, it wont go to update the database until you call the @Remove annotated method. When this call arrives, you will probably get a TransactionRequiredException.

EDIT 1 --------------------------------------------------------------------------------

According to your comments:

  • find is not required to be in a transaction, although, if you want managed object there must be one.

  • The paragraph is about EM the life cycle (3.3 section), in this case, tries to explain that at the end of a method for a transaction-scoped bean, the entities will be detached, but, in the case of extended EM the entities will remains attached.

  • There are 2 insightful paragraphs :

  1. When an EM with an extended persistence context is used, the persist, remove, merge and refresh operations can be called regardless of whether a transaction is active. The effects of these operations will be committed to the database when the extended persistence context is enlisted in a transaction and the transaction commits.

  2. 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).

  • Looks like the method that you initially omit in the question with @TransactionAttribute(REQUIRES_NEW) is the place where the merge occurs successfully. That's why you have no exception.

EDIT 2 --------------------------------------------------------------------------------

After some testing, GF4 has a bug and has been reported > https://java.net/jira/browse/GLASSFISH-20968

EDIT 3 ---------------------------------------------------------------------------------

20/May/2014 : The bug has been marked as : Must Fix for Glassfish 4.0.1.

Community
  • 1
  • 1
Sergio
  • 3,317
  • 5
  • 32
  • 51
  • I think you are wrong. Here is an extract of the JPA specification: "The find method (provided it is invoked without a lock or invoked with LockModeType.NONE) and the getReference method are not required to be invoked within a transaction context. If an entity manager with transaction-scoped persistence context is in use, the resulting entities will be detached; if an entity manager with an extended persistence context is used, they will be managed". The persistence context is extended, so customer should be managed; it is why I made it extended. – user1643352 Jan 30 '14 at 13:36
  • For the second part of your answer: the stateful EJB has another method annotated with @TransactionAttribute(NEW_REQUIRED) and the update will occur when I call that method. It has nothing to do with my question so I didn't write it here. I don't think that a method annotated with @Remove is linked with the update of the database. – user1643352 Jan 30 '14 at 13:46
  • According to the extract of the specification, a transaction is not needed to have a managed object in the case given in my question (contrary to the first item of your EDIT.). So I still don't know why I must add this merge (normally no merge is needed). – user1643352 Jan 30 '14 at 18:30
  • @Remove: If the persistence context is closed, it doesn't mean that there will be a commit. – user1643352 Jan 30 '14 at 18:32
  • I don't think so. An entity retrieved by find is managed by the entity manager. It can be retrieved out of a transaction according to the extract I have quoted. What do you call a managed object? – user1643352 Jan 31 '14 at 20:20
  • Where did you read that a method annotated by @Remove will execute a commit if managed objects have been changed? – user1643352 Jan 31 '14 at 20:30
  • the paragraph that you quoted talks about the life cycle of EM, please refer to 3.3 section. Managed objects, are whom the EM is looking for changes and commit them when the EM is closed (or @Remove is trigerred). Please read the spec as a whole, I think that you are not reading the paragraph under the context, PLEASE refer to 3.3 section. – Sergio Jan 31 '14 at 20:37
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46546/discussion-between-user1643352-and-chechus) – user1643352 Jan 31 '14 at 21:06
0

According to Checkus, it seems to be a bug in GF4: https://java.net/jira/browse/GLASSFISH-20968

user1643352
  • 2,635
  • 2
  • 19
  • 25