Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage transactions with JAX-RS, Spring and JPA

I'm using JAX-RS to provide an HTTP-based interface to manage a data model. The data model is stored in a database and interacted with via JPA.

This allows me to modify the interface to the data model to suit REST clients and mostly seems to work quite well. However, I'm not sure how to handle the scenario where a method provided by a JAX-RS resource requires a transaction, which affects the JPA get, update, commit-on-tx-end pattern, because there is only a transaction wrapping the get operation, so the update is never committed. I can see the same problem occurring if a single REST operation requires multiple JPA operations.

As I'm using Spring's transaction support, the obvious thing to do is to apply @Transactional to these methods in the JAX-RS resources. However, in order for this to work, Spring needs to manage the lifecycle of the JAX-RS resources, and the useage examples I'm aware of have resources being created via `new' when needed, which makes me a little nervous anyway.

I can think of the following solutions:

  1. update my JPA methods to provide a transaction-managed version of everything I want to do from my REST interface atomically. Should work, keeps transactions out of the JAX-RS layer, but prevents the get, update, commit-on-tx-end pattern and means I need to create a very granular JPA interface.
  2. Inject Resource objects; but they are typically stateful holding at least the ID of the object being interacted with
  3. Ditch the hierarchy of resources and inject big, stateless super resources at the root that manage the entire hierarchy from that root; not cohesive, big services
  4. Have a hierarchy of injected, stateless, transaction-supporting helper objects that 'shadow' the actual resources; the resources are instantiated and hold ths state but delegate method invocations to the helper objects

Anyone got any suggestions? It's quite possible I've missed some key point somewhere.


Update - to work around the lack of a transaction around the get, update, commit-on-tx-close flow, I can expose the EntityManager merge(object) method and call it manually. Not neat and doesn't solve the larger problem though.


Update 2 @skaffman Code example: In JPA service layer, injected, annotations work

public class MyEntityJPAService {
...
@Transactional(readOnly=true) // do in transaction
public MyEntity getMyEntity(final String id) {
    return em.find(MyEntity.class, id);
}

In JAX-RS resource, created by new, no transactions

public class MyEntityResource {
...
private MyEntityJPAService jpa;
...
@Transactional // not injected so not effective
public void updateMyEntity(final String id, final MyEntityRepresentation rep) {
    MyEntity entity = jpa.getMyEntity(id);
    MyEntity.setSomeField(rep.getSomeField());
    // no transaction commit, change not saved...
}
like image 622
brabster Avatar asked Mar 07 '12 12:03

brabster


1 Answers

I have a few suggestions

  1. Introduce a layer between your JPA and JAX-RS layers. This layer would consist of Spring-managed @Transactional beans, and would compose the various business-level operations from their component JPA calls. This is somewhat similar to your (1), but keeps the JPA layer simple.

  2. Replace JAX-RS with Spring-MVC, which provides the same (or similar) functionality, including @PathVariable, @ResponseBody, etc.

  3. Programmatically wrap your JAX-RS objects in transactional proxies using TransactionProxyFactorybean. This would detct your @Transactional annotations and generate a proxy that honours them.

  4. Use @Configurable and AspectJ LTW to allow Spring to honour @Transactional even if you create the object using `new. See 8.8.1 Using AspectJ to dependency inject domain objects with Spring

like image 117
skaffman Avatar answered Sep 30 '22 18:09

skaffman