2

I'm trying to use aspectj with compile time weaving to support annotations like Spring's @Transactional and @Configurable. I'm using the org.springframework.orm.jpa.JpaTransactionManager transaction manager and what I see in the logs when I try to call entityManager.persist(entity) in my GenericDAO is something like this:

insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] TRACE org.hibernate.loader.Loader - Bound [6] parameters total
insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] TRACE org.hibernate.loader.Loader - processing result set
insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] DEBUG org.hibernate.loader.Loader - result set row: 0
insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] TRACE o.h.t.descriptor.sql.BasicExtractor - found [1] as column [id3_]
insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] DEBUG org.hibernate.loader.Loader - result row: EntityKey[com.vendio.insurance.domain.db.InsuranceRate#1]
insurance-module-0.1-SNAPSHOT 19:57:55.199 [http-bio-8084-exec-49] TRACE org.hibernate.loader.Loader - done processing result set (1 rows)
insurance-module-0.1-SNAPSHOT 19:57:55.200 [http-bio-8084-exec-49] TRACE org.hibernate.loader.Loader - total objects hydrated: 0
insurance-module-0.1-SNAPSHOT 19:57:55.200 [http-bio-8084-exec-49] DEBUG o.h.e.StatefulPersistenceContext - initializing non-lazy collections
insurance-module-0.1-SNAPSHOT 19:57:55.200 [http-bio-8084-exec-49] TRACE org.hibernate.impl.SessionImpl - after transaction completion
insurance-module-0.1-SNAPSHOT 19:57:55.201 [http-bio-8084-exec-49] TRACE o.s.t.s.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@5ec859c1] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@337cbe84] bound to thread [http-bio-8084-exec-49]
insurance-module-0.1-SNAPSHOT 19:57:55.209 [http-bio-8084-exec-49] DEBUG org.hibernate.SQL - select sequence_next_hi_value from hibernate_sequences where sequence_name = 'registered_policy' for update
insurance-module-0.1-SNAPSHOT 19:57:55.210 [http-bio-8084-exec-49] DEBUG org.hibernate.SQL - update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'registered_policy'
insurance-module-0.1-SNAPSHOT 19:57:55.218 [http-bio-8084-exec-49] TRACE o.s.t.s.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@5ec859c1] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@337cbe84] bound to thread [http-bio-8084-exec-49]

so the per-table hibernate sequence gets updated but my entity is not inserted into the database.

If I add entityManager.flush() an exception appears stating "no transaction is in progress".

What is going on here?!

My GenericDAO class looks like this:

public class GenericDAO<T extends Persistable> { 
 @PersistenceContext 
 protected EntityManager entityManager; 

 @PersistenceUnit 
 protected EntityManagerFactory entityManagerFactory;

 @Transactional
 public void saveOrUpdate(T entity) {
     entityManager.persist(entity);
 }

}

I call the saveOrUpdate method from a web-service exported with the WSSpringServlet.

P.S.: Also my Maven config looks like this:

   <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.4</version>
        <configuration>
            <complianceLevel>1.6</complianceLevel>
        <showWeaveInfo>true</showWeaveInfo>
            <aspectLibraries>
                <aspectLibrary>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                </aspectLibrary>
            </aspectLibraries>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
        </executions>
    </plugin>

When compiling I get something which looks decent enough (my aspect gets applied):

    Join point 'method-call(void javax.persistence.EntityManager.persist(java.lang.Object))'
 in Type 'com.vendio.insurance.dao.GenericDAO' (GenericDAO.java:28) 
advised by afterThrowing advice from 'org.springframework.orm.jpa.aspectj.JpaExceptionTranslatorAspect' 
(spring-aspects-3.1.0.RELEASE.jar!JpaExceptionTranslatorAspect.class:14(from JpaExceptionTranslatorAspect.aj))

And my relevant Spring config is:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<context:component-scan base-package="com.vendio.insurance" />
<context:spring-configured/>
 <!--    <bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager"/>
</bean>-->

<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />

So everything looks decent, but I can't find an answer of why this doesn't work...

Mircea D.
  • 331
  • 4
  • 14
  • Is there a question in here somewhere? What does "not working" look like beyond this stack trace? – duffymo Feb 03 '12 at 18:04
  • Sorry for that... I was just trying to see if stackoverflow bans me or not (happened before) before I wasted my time writing the question. It seems it worked and I've edited my question to make it clear. – Mircea D. Feb 03 '12 at 18:06
  • Did you open a Hibernate session? If not, that's your problem. – duffymo Feb 03 '12 at 18:07
  • I'm using JPA here. How do I do that with JPA? Do I have to? – Mircea D. Feb 03 '12 at 18:13
  • Yes, you have to. I don't have the details, but this might: http://www.avaje.org/jpa.html – duffymo Feb 03 '12 at 18:16
  • _EntityManager in JPA acts as a Session object for a user._ - I get mine Autowired by Spring: `public class GenericDAO { @@PersistenceContext protected EntityManager entityManager; @@PersistenceUnit protected EntityManagerFactory entityManagerFactory;` – Mircea D. Feb 03 '12 at 18:18
  • You need one session per request. Is that how you've configured Spring, or is it a singleton? – duffymo Feb 03 '12 at 18:24
  • Actually it wasn't a singleton at all, so this really **isn't the issue** (but thank you very much @duffymo for trying). Apparently Spring handles the thread safety by injecting a transactional em: [_You can avoid this by requesting a transactional EntityManager (also called "shared EntityManager" because it is a shared, thread-safe proxy for the actual transactional EntityManager) to be injected instead of the factory_] (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/orm.html) – Mircea D. Feb 03 '12 at 20:38

3 Answers3

3

I've found my answer following this link: http://forum.springsource.org/showthread.php?18953-DispatcherServlet-and-ContextLoaderListener.

The problem was caused by the fact that I was also using Spring MVC and I was creating without knowing two almost identical Spring contexts. Thus the transaction was managed by the transaction manager in the first context (the one receiving the JAX-WS call) but the entity manager I was calling was managed by the second context (with a different transaction manager).

The solution was to isolate the small reduced context definition for the DispatcherServlet and leave the rest of the beans to be managed by ContextLoaderListener:

<listener> 
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener> 
<context-param> 
 <param-name>contextConfigLocation</param-name> 
 <param-value>/WEB-INF/spring/application-context.xml</param-value> 
</context-param>

<servlet> 
 <servlet-name>spring-mvc-dispatcher-servlet</servlet-name> 
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> 
 <init-param> 
 <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/servlet-context.xml</param-value> 
 </init-param> 
</servlet> 

Since I used annotation based MVC (with @Controller annotation) I also had to reduce the scope of the context:component-scan's base-package in the "servlet" context.

Below is the quote from the link that saved my day:

DispatcherServlet will always load its own configuration file using -servlet.xml. It is intended that this file will contain web components such as Controllers, ViewResolvers and LocaleResolvers - but no middle tier components.

The ContextLoaderListener is then used to load the files containing your middle tier and data tier components. Spring will merge all these components into an ApplicationContext making your middle tier components accessible from your web tier components. >Rob Harrop Lead Engineer, dm Server

0

so the per-table hibernate sequence gets updated but my entity is not inserted into the database.

I was experiencing the same symptom even though there was no problem of two identical Spring contexts. The forceAjcCompile option resolved the issue:

            ...
            <java.version>1.6</java.version>
            <aspectj.version>1.7.0</aspectj.version>
            ...
            <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.6</version>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <showWeaveInfo>false</showWeaveInfo>
                <verbose>false</verbose>
                <complianceLevel>${java.version}</complianceLevel>
                <forceAjcCompile>true</forceAjcCompile>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <Xlint>ignore</Xlint>
            </configuration>
        </plugin>
Ritesh
  • 7,472
  • 2
  • 39
  • 43
0

If you use EntityManager you won't have to deal with any hibernate issues in your code. That's exactly what jpa is for.

I had the same problem some time ago.

Your EntityManager has to be injected with @PersistenceContext. getEntityManager() won't do.

And your annotated functions have to be public.

AlexS
  • 5,295
  • 3
  • 38
  • 54
  • Well, I already have it like that (field-based injection) as I wrote in my previous comments: `public class GenericDAO { @ PersistenceContext protected EntityManager entityManager; @ PersistenceUnit protected EntityManagerFactory entityManagerFactory;` – Mircea D. Feb 03 '12 at 20:07
  • Since it seems StackOverflow is blocking my answers here it is below: I've found my answer following this link: http://forum.springsource.org/showthread.php?18953-DispatcherServlet-and-ContextLoaderListener. The problem was caused by the fact that I was also using Spring MVC and I was creating without knowing **two almost identical Spring contexts**. Thus the transaction was managed by the transaction manager in the first context (the one receiving the JAX-WS call) but the entity manager I was calling was managed by the second context (with a different transaction manager). – Mircea D. Feb 07 '12 at 17:34
  • **The solution was to isolate the small reduced context definition for the DispatcherServlet and leave the rest of the beans to be managed by ContextLoaderListener:** org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/spring/application-context.xml – Mircea D. Feb 07 '12 at 17:35