Matt Warrick
Matt Warrick

Reputation: 1488

JPA - only first commit failed, but should failed all

please can somebody help me to explain the following (for me) very strange JPA behaviour. I intentionally change primary key of entity which is prohibed in JPA.

So first commit correctly throws "Exception Description: The attribute [date] of class [some.package.Holiday] is mapped to a primary key column in the database. Updates are not allowed.".

But second (third, fourth, ...) succeed...! How is this possible?!

Holiday h1 = EM.find(Holiday.class, new GregorianCalendar(2011, 0, 3).getTime());

try {
    EM.getTransaction().begin();
    h1.setDate(new GregorianCalendar(2011, 0, 4).getTime());
    EM.getTransaction().commit();
    System.out.println("First commit succeed");
} catch (Exception e) {
    System.out.println("First commit failed");
}

try {
    EM.getTransaction().begin();
    EM.getTransaction().commit();
    System.out.println("Second commit succeed");
} catch (Exception e) {
    System.out.println("Second commit failed");
}

It will printout:

First commit failed
Second commit succeed

OMG, how is this possible?!

(Using EclipseLink 2.2.0.v20110202-r8913 with MySQL.)

Upvotes: 4

Views: 1148

Answers (1)

Vineet Reynolds
Vineet Reynolds

Reputation: 76719

The failure of the commit operation for the first transaction has no bearing on the second transaction. This is due to the fact that when the first commit fails, the EntityTransaction is no longer in the active state. When you issue the second em.getTransaction().begin invocation, a new transaction is initiated that does not have any knowledge of the first.

It is important to note that although your code may use the same EntityTransaction reference in both cases, it is not necessary that this class actually represent the transaction. In the case of EclipseLink, the EntityTransaction reference actually wraps an EntityTransactionWrapper instance that further uses a RepeatableUnitOfWork, the latter two classes being provided by EclipseLink implementation and not JPA. It is the RepeatableWriteUnitOfWork instance that actually tracks the collection of changes made to entities that will be merged into the shared cache (and the database). When the first transaction fails, the underlying UnitOfWork is invalidated, and new UnitOfWork is established when you start the second EntityTransaction.

The same will apply to most other JPA providers as the EntityTransaction class is not a concrete final class. Instead, it is an interface that is typically implemented by another class in the JPA provider, and which may likewise wrap a transaction thereby requiring clients to use the EntityTransaction reference instead of directly working with the underlying transaction (which may be a JTA transaction or a resource-local transaction).

Additionally, you ought to remember that:

  • EntityTransaction.begin() should be invoked only once. Invoking it a second time will result in an IllegalStateException exception being thrown as it cannot be invoked when a transaction is active. So, the fact that you are able to invoke it the second time, implies that the first transaction is no longer active.
  • If you require the changes performed in the context of the first transaction to be made available to the second, you must merge the entities back into the shared context in the second transaction, after they've been detached by the first. While, this may sound ridiculous, you ought to remember that detached entities can be modified by clients (read, end-users) before they are merged back, so the changes made by the end users may be retained, while mistakes (like the modification of the primary keys) may be corrected in the interim.

Upvotes: 1

Related Questions