John
John

Reputation: 896

Why is no TransactionRequiredException thrown when @Transactional is missing?

I've got a Spring/JPA configuration with Hibernate as the persistence provider. However I don't understand why there's no TransactionRequiredException thrown when I call save() on the following DAO code without an open transaction (there's no @Transactional at the DAO/service):

@Repository
public class UserDao {

    @PersistenceContext
    private EntityManager entityManager;

    public void save(User user) {
        entityManager.persist(user);
    }
}

As expected the entity is not saved but why is there no exception thrown? The javadoc for persist says that persist() should throw a "TransactionRequiredException - if there is no transaction when invoked on a container-managed entity manager of that is of type PersistenceContextType.TRANSACTION".

Debugging shows that Hibernate has an open session. Is that the expected behavior? BTW: I get the same behavior when using EclipseLink.

Here's my applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"  xmlns:context="http://www.springframework.org/schema/context"   xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<context:component-scan base-package="com.foo.bar" />
<context:annotation-config />
<tx:annotation-driven />

<!-- Configure jdbc.properties -->
<bean id="propertyConfigurer"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
    p:location="/WEB-INF/jdbc.properties" />

<!-- Data Source configuration -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
    p:url="${jdbc.databaseurl}" p:username="${jdbc.username}" p:password="${jdbc.password}" />

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="HSQL" />
            <property name="showSql" value="true" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.hbm2ddl.auto">create</prop>
        </props>
    </property>
</bean>

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

<bean id="persistenceExceptionTranslationPostProcessor"
    class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

EDIT Hibernate version (from pom.xml)

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.3.5.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.3.5.Final</version>
    <exclusions>
        <exclusion>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
        </exclusion>
        <exclusion>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hibernate.javax.persistence</groupId>
    <artifactId>hibernate-jpa-2.0-api</artifactId>
    <version>1.0.1.Final</version>
</dependency>

Upvotes: 3

Views: 1577

Answers (2)

Oliver Drotbohm
Oliver Drotbohm

Reputation: 83171

The answer is pretty clear and concise (you actually discovered it yourself): JPA requires a transaction being in progress for the call to EntityManager.persist(…). However your code is not set up to be run inside a transaction. Your configuration looks sane but you're missing an @Transactional on either the DAO or any layer above.

As the original question was why the exception is not thrown, I looked up the spec again and discovered that in 7.9.1 it clearly requires the container to throw that exception in a container managed persistence context. Running JPA in Spring is a bit of an in-between scenario, as we clearly don't require JTA and the like but it's understandable that by injecting the EntityManager you assume to be in a managed environment. I filed SPR-11923 to improve that in our JPA infrastructure support.

Side track: When working with Spring and JPA, you might wanna have a look at the Spring Data JPA project, which significantly eases the process of building a data access layer, including sane defaults for transaction processing on the repository layer (see this guide for example). Using that would've prevented you from running into the situation in the first place.

Upvotes: 2

Chris
Chris

Reputation: 21165

The javadoc states that an exception is expected "if there is no transaction when invoked on a container-managed entity manager of that is of type PersistenceContextType.TRANSACTION" Your context is managed by Spring which I don't believe is a Java EE container, and so the same rule does not apply, and the context is probably defaulting to extended so that it holds objects until joined to a transaction. If you want to force an exception when a transaction isn't available, call em.flush() after the persist.

Upvotes: 0

Related Questions