Search code examples
jpatransactionsejbconstraintsjta

JTA transactions committing too early, fails when using constraints


We have a Java EE application running in Glassfish 3.1, where we have our JPA models (using EclipseLink) organized like this:

Customer
 -> String firstName
 -> String lastName
 -> Address adress
 -> List<Attribute> attributes
 -> Int age

Address
-> String street
-> Int zip
-> String city

Attribute
 -> String code
 -> String name

Most of the attributes like firstName and lastName are annotated with @Column(nullable=false). Now we do:

@Stateless
public class CustomerController {

  @PersistenceContext(unitName = "CustomerService")
  private EntityManager em;

  @EJB
  private AttributeController attributeController;

  public String createCustomer() {
    Customer customer = new Customer();
    customer.firstName = "Hans";
    customer.lastName = "Peter";

    customer.address = new Address();
    customer.adress.street = ...

    customer.attributes = new ArrayList<Attribute>();
    customer.attributes.add(attributeController.getByCode("B"));
    customer.attributes.add(attributeController.getByCode("T"));

    customer.age = 27;

    em.persist(customer);
  }
}

This works for small classes like the one above, but we have now introduced more objects which are related to the customer like attributes with a @OneToMany and are loaded from other @EJBs like the attributeController.

For "big" models it now seems like a transaction is commited right in the process of still loading related objects, since we get a ERROR: null value in column "age" violates not-null constraint. Since we use JTA container managed transactions and have not set @TransactionAttribute to something other than the default REQUIRED, we do not control the transaction directly and have no idea what is going wrong here.

Is there a certain amount of "units of work" that can be oben before a commit takes place? Are we loading the related objects wrong? Have we made some other major mistake?

As soon as we omit the nullable=false constraints, everything works fine...


Solution

  • Your entities are probably getting auto-flushed when queries are occurring. According to the JavaDocs for FlushModeType:

    When queries are executed within a transaction, if FlushModeType.AUTO is set on the Query object, or if the flush mode setting for the persistence context is AUTO (the default) and a flush mode setting has not been specified for the Query object, the persistence provider is responsible for ensuring that all updates to the state of all entities in the persistence context which could potentially affect the result of the query are visible to the processing of the query. The persistence provider implementation may achieve this by flushing those entities to the database or by some other means. If FlushModeType.COMMIT is set, the effect of updates made to entities in the persistence context upon queries is unspecified.

    Basically, if you do a query, and any of your uncommitted entities (which don't have all their members set yet) would be eligible to be a result of that query, then the persistence implementation must flush them to the database (or do something with equivalent effect), thus causing an exception to be thrown because the nullable constraints are invalid. It seems a little unintuitive to me, too. I wouldn't have known about it if we hadn't run into a very similar issue in our application.

    It sounds like you just want to use EntityManager.setFlushMode() or Query.setFlushMode() to set the flush mode to COMMIT.