Dave Taubler
Dave Taubler

Reputation: 1091

Switched to JPA, now I always get TransactionRequiredException on any insert

I have a Spring4 web application. Initially I was using the Hibernate SessionFactory, and developing using the Spring Hibernate API. Everything worked fine. Foolishly perhaps I decided recently to switch to using JPA, with Hibernate remaining as my provider. I reconfigured my Spring settings and rewrote much of my code. Initially a tested so that all of my database reads worked, which they do. Then I tried database writes, and they all fail like so:

javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:970)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:342)
    at com.sun.proxy.$Proxy47.flush(Unknown Source)
    at com.taubler.oversite.dao.impl.EntityDaoImpl.insert(EntityDaoImpl.java:65)
    ...

Keep in mind that my code worked fine when using HibernateTemplate, SessionFactory, HibernateTransactionManager, etc. My business logic classes, as well as my DAOs, are annotated with @Transactional just as before.

It looks as though Hibernate is attempting to create a transaction, as I see the following in my logs just before the stack trace:

2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - initial autocommit status: true
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - disabling autocommit
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.internal.SessionImpl - Opened session at timestamp: 14115372286
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.AbstractSaveEventListener - Transient instance of: com.taubler.oversite.entities.EmailAddress
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.DefaultPersistEventListener - Saving transient instance
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.AbstractSaveEventListener - Saving [com.taubler.oversite.entities.EmailAddress#<null>]
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.spi.IdentifierValue - ID unsaved-value: null
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Delaying identity-insert due to no transaction in progress
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.internal.SessionImpl - Opened session at timestamp: 14115372287
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - rolling back
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - rolled JDBC Connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - re-enabling autocommit
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl - after transaction completion
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - after transaction completion
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Closing session
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Closing logical connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.JdbcResourceRegistryImpl - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcResourceRegistryImpl@2c4e3947]
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Releasing JDBC connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Released JDBC connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Logical connection closed

Here is some relevant code. First, snippets from my spring config XML:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="packagesToScan" value="com.taubler.oversite.entities" />
      <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </property>
      <property name="jpaProperties">
         <props>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.jdbc.batch_size">20</prop>
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>               
         </props>
      </property>
</bean>

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven transaction-manager="txManager"/>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${db.driver}" />
    <property name="url" value="${db.url}" />
    <property name="username" value="${db.username}" />
    <property name="password" value="${db.password}" />
</bean>

A sample business logic (manager) class. This class is @Autowired into the SpringMVC Controller, so the Controller is calling the proxy:

...

@Autowired
private EmailAddressDao emailAddressDao;
...

@Override
@Transactional
public EmailAddress addEmailAddress(User user, String email) {  
    EmailAddress emailAddress = new EmailAddress(user, email);
    emailAddress.setMain(false);
    emailAddress.setValidated(false);
    emailAddressDao.insert(emailAddress);
    this.initiateEmailValidation(emailAddress);
    return emailAddress;
}

And the DAO called by that manager:

...

@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

protected final EntityManager getEntityManager() {
    return entityManagerFactory.createEntityManager();
}

public boolean insert(Entity o) {
    o.setCreated(new Date());
    this.getEntityManager().persist(o);
    this.getEntityManager().flush();
    return true;
}

I've tried different variations of this. Initially both the DAO and the Manager method were annotated with @Transactional(propagation=REQUIRED); this was how it worked with pure Hibernate. I've tried removing the propagation setting, annotating only the Manager method, annotating only the DAO method... nothing works.

Any thoughts? There seems to be something fundamentally different between HibernateTransactionManager and JpaTransactionManager.

Upvotes: 1

Views: 651

Answers (2)

M. Deinum
M. Deinum

Reputation: 124516

@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

protected final EntityManager getEntityManager() {
    return entityManagerFactory.createEntityManager();
}

The problem is you are creating an entity manager yourself, don't. Just inject the EntityManager instead of the EntityManagerFactory and annotate with @PersistenceContext instead of @PersistenceUnit. Spring will take care of making it a bound to the current transaction.

@PersistenceContext
private EntityManager entityManager;

protected final EntityManager getEntityManager() {
    return entityManger;
}

If you really want to keep injecting the EntityManagerFactory use the getTransactionalEntityManager method of the EntityManagerFactoryUtils to get the Spring managed EntityManager instance.

protected final EntityManager getEntityManager() {
     return EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory);
}

Also the fact that it worked with "plain" hibernate doesn't mean your setup had to be right. You mentioned you used the HibernateTemplate which basically could work without proper transaction setup as it would start a new transation just for the action at hand. So it could very well be that the application seemed to work correctly where it actually didn't. Maybe you had multiple transactions where you expected one (from the service layer).

Another note is that your code could be dangerous

public boolean insert(Entity o) {
    o.setCreated(new Date());
    this.getEntityManager().persist(o);
    this.getEntityManager().flush();
    return true;
}

This, in your case, could result in 2 different EntityManagers being created so you might have been flushing another one as you persisted in. Next to that you shouldn't be calling flush as that will be done by the transaction ending.

Upvotes: 4

Ralph
Ralph

Reputation: 120781

You need to inject the EntityManager directly with @PersistenceContext annotation, instead of the PersistenceUnit

Upvotes: 2

Related Questions