Propagation.REQUIRES_NEW does not create a new transaction in Spring with JPA
Asked Answered
A

4

27

I have the following scenario. I am using JPA, Spring:

@Autowired
SampleService service;

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void PerformLogic(LogicData data) throws SIASFaultMessage
{
    SampleObject so = createSampleObject();

    try{
        .//do some logic to persist things in data
        .
        .
        persistData(data);
        .
        .
        .


        updateSampleObject(so);     
    }
    catch(Exception){
        updateSampleObject(so);     
        throw new SIASFaultMessage();
    }

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public createSampleObject()
{
    SampleObject so = new SampleObject();

    .
    .//initialize so
    .

    service.persist(so);        
    return so;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public updateSampleObject(SampleObject so)
{               
    service.persist(so);        
    return so;
}

When everything works fine, data is persisted in database without problems. However, when an exception is thrown, I need that the method updateSampleObject(so) persist the information in the database. This is not what is happening. If an exception is thrown the method updateSampleObject gets rolled back also, which is not what I need. I need that these two methods (createSampleObject and updateSampleObject) get persisted all the time, no matter whether an exception got thrown or not. How can I achieve this?

Moreover, if I anotate the methods createSampleObject and updateSampleObject with:

@Transactional(propagation = Propagation.NEVER)

the idea is that an exception is thrown and I get no exception thrown. Where is the problem? Analizing the logs I see this line:

org.springframework.orm.jpa.JpaTransactionManager  ==> Creating new transaction with name [com.test.PerformLogic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT....

which means this transaction is created, but I see no hint of the other transaction.

This is the part of my configuration file for Spring regarding transactions

<bean id="myDataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="packagesToScan" value="cu.jpa"/>
    <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">${hdm2ddl.auto}</prop>
        </props>
    </property>
    <property value="/META-INF/jpa-persistence.xml" name="persistenceXmlLocation"/>
    <property name="persistenceUnitName" value="jpaPersistenceUnit"/>
</bean>

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

<tx:annotation-driven transaction-manager="transactionManager"/>
Aorist answered 12/2, 2015 at 14:48 Comment(0)
P
67

Spring transactions are proxy-based. Here's thus how it works when bean A causes a transactional of bean B. A has in fact a reference to a proxy, which delegates to the bean B. This proxy is the one which starts and commits/rollbacks the transaction:

A ---> proxy ---> B

In your code, a transactional method of A calls another transactional method of A. So Spring can't intercept the call and start a new transaction. It's a regular method call without any proxy involved.

So, if you want a new transaction to start, the method createSampleObject() should be in another bean, injected into your current bean.

This is explained with more details in the documentation.

Prostate answered 12/2, 2015 at 20:20 Comment(7)
Thanks, that was it. I will take a closer look to Spring documentation. Do you know if there is a way for the transactions to work not on proxy-based, but on the method?Aorist
@AlfredoA. It's possible if you use AspectJ load-time weaving to byteweave your code, thereby obviating the need for proxies as mentioned above. That way, transactional method foo() of class A can call transactional method bar(), also of class A, and bar() will run inside its own transaction. For more info on this, consult the Spring Framework documentation linked above.Almeida
Thanks a ton. I searched a lot when transaction is committed as I have 2 different transactions where one is dependent on other's result. It worked with "Required" & "Requires_New" but only when I put then in different beans.Dated
have AspectJ-based transaction any disadvantages? when should I use proxy and when AspectJ style?Whity
Thanks for ending several hours of frustration. Flowers should be on the way.Israelite
I have a question, (after all this years :) ) Does this means that the private methods are always executed in the transaction of the method that calls them (if that method is public and annotated with @Transactional)? For example in code above does createSampleObject(), persistData(data); and updateSampleObject(so); are executed in the scope of the transaction that is open at PerformLogic(LogicData data) method?Rental
@JB Nizet you made my day!Blancmange
D
9

My guess is that since both methods are in the same bean, the Spring's AOP does not have a chance to intercept the create/updateSampleObject method calls. Try moving the methods to a separate bean.

Dottydoty answered 12/2, 2015 at 20:16 Comment(1)
Thanks a lot, this was it. I would mark it as correct, but I just read the other one first.Aorist
I
0

Please create a bean for the same class(self) and use bean.api(which requires requires_new). It works.

Interview answered 22/4, 2019 at 8:34 Comment(0)
T
0

In certain cases, even if you have service a -> service b and you have a @Transactional(propagation = Propagation.REQUIRES_NEW) on the service b method(which needs to be public by the way!), locking rows etc may fail.
This is usually if auto-commit: false is not specified in your spring(usually hikari pool) datasource configuration. The reason is that any select for update or similar statements are executed immediately and the database does not lock the row.

Talithatalk answered 19/11, 2022 at 18:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.