Bevor
Bevor

Reputation: 8605

JPA: How to use transactions with JTA EntityManager?

First of all, this solution is no option for me, because I can't change the persistence-unit.

My problem is that I use a JTA EntityManager but I need for exactly one use case something like a transaction:

public boolean saveWithResult(PointsValidityPeriod pointsValidityPeriod)
{
    //TODO use transaction here 
    super.save(pointsValidityPeriod);

    if (updatePrevious(pointsValidityPeriod.getValidFrom()) != 1)
    {
        logger.error("Update of Period was not possible, because UPDATE returned no single result.");

        return false;
    }

    pointsValidityPeriodEvent.fire(pointsValidityPeriod);

    return true;
}

Save method (which I can't change):

public void save(T entity)
{
    getEntityManager().persist(entity);
}

You see, that there is a save invocation, but this save must be rolled back if the update went wrong, so how can I achieve that? Any ideas?

Upvotes: 1

Views: 10549

Answers (2)

Bevor
Bevor

Reputation: 8605

I have the solution but I don't know why this works. Maybe someone could explain it to me. In my backing bean there is the save method. There are 2 entities. getEntity() is the current entity used by the backing bean, currentValidityPeriod is another instance of that Entity which holds the latest database state of the entity, fetched by currentValidityPeriod = pointsValidityService.findCurrent(); findCurrent() is just a method which executes a TypedQuery:

public PointsValidityPeriod findCurrent()
{
    TypedQuery<PointsValidityPeriod> validityPeriodQuery = entityManager.createNamedQuery(PointsValidityPeriod.FindCurrent, PointsValidityPeriod.class);

    return validityPeriodQuery.getSingleResult();
}

This is the save method invoked by the view. As you can see, I only pass the current Entity to the service save method. In the previous Entity (currentValidityPeriod), I set another value.

@Override
public void save()
{       
    Date validFrom = new DateTime(DateTimeZone.forID("Europe/Vienna")).toDate();
    getEntity().setValidFrom(validFrom);

    currentValidityPeriod.setValidThru(validFrom);

    pointsValidityService.save(getEntity());

    addSavedSuccessfullyMessage();
}

This is the service method which will be invoked:

@Override
public void save(PointsValidityPeriod pointsValidityPeriod)
{
    super.save(pointsValidityPeriod);
}

...and Superclass's save method:

public void save(T entity)
{
    getEntityManager().persist(entity);
}

Why does the Entity Manager persist the new Entity and update the old one? I just pass ONE Entity to the Entity Manager. Although this is the expected behavior, I'd like to know why this works.

Upvotes: 0

Piotr Nowicki
Piotr Nowicki

Reputation: 18174

It all depends on your methods transactional annotations. If you don't have any, than by default the transaction boundary is set to the business method, as pointed by JB.

So, the whole method is one transaction. Normally, the transaction management of save method would be important (it's a difference if it's REQUIRED or REQUIRES_NEW), but in this case you're making a local method call -- not a business method call, so any transaction management settings for save method doesn't apply here.

Therefore, if the update fails you can either base on automatic transaction rollback (if it's an EntityManager call which generates exception that rollback the Tx) or you can manually inject SessionContext and invoke setRollbackOnly() on it, something between the lines:

@Resource
SessionContext ctx;

public boolean saveWithResult(PointsValidityPeriod pointsValidityPeriod)
{
   // ...
    if (updatePrevious(pointsValidityPeriod.getValidFrom()) != 1)
    {
        // ...
        ctx.setRollbackOnly();
        // ...
    }
}

Upvotes: 1

Related Questions