0
votes

My project is a WAR project generated with seam-gen. It contains a RESTEasy web service class like this (simplified, only the relevant parts):

@Scope(ScopeType.APPLICATION)
public abstract class RestService {

    @In
    protected EntityManager entityManager;

    @GET
    @POST
    @Produces("application/json")
    public Object proxy() {
        // invokes various subclass methods
        // based on request parameters
        // and returns the result
    }
    // further method and logic
}

And:

@Path("/my")
@Name("myRestService")
public class MyRestService extends RestService {

    public Object login(/*...*/) {
        User user = getUser(email);

        // ...

        Token token = user.getToken();
        if (token != null) {
            entityManager.remove(token);
        }
        token = new Token();
        entityManager.persist(token);
        user.setToken(token);
        user.setLastlogin(new Date());
        entityManager.persist(user);

        // ...
    }

    private User getUser(String email) {
        try {
            return (User) entityManager
                    .createQuery("FROM User WHERE UPPER(email) = UPPER(:email)")
                    .setParameter("email", email)
                    .getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }
}

If i invoke the login method through a web browser, it finds the correct user (based on the get params), instantiates a Token for it (i can see at the STDOUT of Hibernate asking the databse for the next sequence), but the persist() method does not save the Token to the database, neither the modifications of the User object (token id, last login date).

I have googled this for two days now, here's what i could figure out:

  • my project uses SEAM managed transactions (components.xml):

    <persistence:managed-persistence-context name="entityManager" auto-create="true"
                       persistence-unit-jndi-name="java:/MyEntityManagerFactory"/>
    
  • my project uses JTA for transaction handling (persistence.xml):

    <persistence-unit name="MyProject" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider> ...
    
  • the EntityManager.persist() does NOT commit changes to the database, just queues changes to the current transaction (?)

  • the SEAM managed Transactions are tied to conversations by default

I tried to use .flush(), an exception was thrown saying that there is no transaction in progress.

I tried to use .joinTransaction() and .getTransaction().begin(), another exception was thrown saying that a JTA EntityManager can not access transactions.

Also tried to use different scope types on the class, or use the @Transactional annotation on my login() method, no luck.

Also tried to inject the EntityManager with the @PersistenceContext annotation, this resulted in an exception saying @PersistenceContext can only be used with session beans.

Also tried to mark my class as @Stateless, this resulted that i could not reach my service (404).

How should i persist my Entities within a RESTEasy service with EntityManager?

System specs:

  • JBoss 5.1.0 GA
  • SEAM 2.2.1 Final
  • Postgres 8.3

Please note that i'm totally new and inexperienced with JavaEE/JBoss/SEAM.

Any comment would be useful! Thanks.

2

2 Answers

1
votes

You are detaching a managed field:

entityManager.remove(token);

Creating a new one:

token = new Token();

But here:

entityManager.persist(token);

Token has a relationship with User (I don't what that relationship is - oneToMany, oneToOne, are you cascading from User?, fetching, etc), but just calling persist on token does not re-establish that relationship. Take a look here http://www.objectdb.com/java/jpa/persistence/crud

0
votes

The transactional annotation is important on the login method. This will ensure that the transaction interceptor is creating a transaction if neccessary. (if there is no transaction yet). The easiest way to find out if the interceptor is applied would be to debug into the login method and check the stack. I'm not sure how the class is called but i will update this post as soon as i'm at work to check it out. If this interceptor is not there it means that you are not using seam transactions. The extract of your components.xml is not showing that you do so.

Martin

Updated: So here's the stacktrace. Take a look at the TransactionInterceptor if you do not have this in your stack you have no transaction management.

Daemon Thread [http-18081-1] (Suspended (breakpoint at line 170 in QueryHome))  
    QueryHome.getCandidatesCount() line: 170    
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39  
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25  
    Method.invoke(Object, Object...) line: 597  
    Reflections.invoke(Method, Object, Object...) line: 22  
    RootInvocationContext.proceed() line: 32    
    SeamInvocationContext.proceed() line: 56    
    RollbackInterceptor.aroundInvoke(InvocationContext) line: 28    
    SeamInvocationContext.proceed() line: 68    
    BijectionInterceptor.aroundInvoke(InvocationContext) line: 77   
    SeamInvocationContext.proceed() line: 68    
    ConversationInterceptor.aroundInvoke(InvocationContext) line: 65    
    SeamInvocationContext.proceed() line: 68    
    TransactionInterceptor$1.work() line: 97    
    TransactionInterceptor$1(Work<T>).workInTransaction() line: 61  
    TransactionInterceptor.aroundInvoke(InvocationContext) line: 91 
    SeamInvocationContext.proceed() line: 68    
    MethodContextInterceptor.aroundInvoke(InvocationContext) line: 44   
    SeamInvocationContext.proceed() line: 68    
    JavaBeanInterceptor(RootInterceptor).invoke(InvocationContext, EventType) line: 107 
    JavaBeanInterceptor.interceptInvocation(Method, Object[]) line: 185 
    JavaBeanInterceptor.invoke(Object, Method, Method, Object[]) line: 103