问题
My DAO interface is simple:
import org.springframework.transaction.annotation.Transactional;
@Component
@Transactional
public interface TTestDao {
@Transactional()
public void first();
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void second() ;
}
ad impl methods:
@Override
public void first() {
entityManager.persist(new TableTest1().setName("data1"));
this.second();
}
public void second() {
entityManager.persist(new TableTest1().setName("data2"));
throw new RuntimeException(); // it roll backs data2 and data1
}
First method invokes second method. Error occurs in second method.
At the moment, if I call first()
, all persisted information will be rolled back. But why does this happen? second()
method is in a new transaction, and I need to keep the first method's data in the db.
In the other words, I need to always persist the first method's data, but roll back only second methods data. I want to write data1 always.
Do I have anything wrong?
I have such a db configuration in SPRING:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@127.0.0.1:1521:xe"
p:username="dev" p:password="****" >
</bean>
<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>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<context:component-scan base-package="ge.ddrc.transport.persistance.entity">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />
My App is running in tomcat (if it makes any sences)
回答1:
@StijnGeukens is close. Spring does in fact use proxies to handle the transactions, and by default calling one transactional method from within another would not result in a new transaction being created. However, in this case propogation is being specifically set to explicitly require a new transaction being created. There should be 2 transactions being created:
T1: create
T2: create
Exception occurs here
T2: commit
T1: commit
As you can see, the exception is occurring before either transaction reaches its commit point. Since it isn't handled in First
, execution wouldn't ever reach the commit point for T1
. That would result in the top-level transaction being rolled back.
However, it is possible that you could handle the exception in First
and it still wouldn't work. From the docs I linked to above:
Note: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available it to it (which is server-specific in standard J2EE).
So your propagation=REQUIRES_NEW
is likely not actually doing what it says it does because it doesn't like your TransactionManager
. Welcome to Spring.
Update From the JpaTransactionManager docs:
On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0 Savepoints. The
AbstractPlatformTransactionManager.setNestedTransactionAllowed(boolean) "nestedTransactionAllowed"
flag defaults to "false", though, as nested transactions will just apply to the JDBC Connection, not to the JPA EntityManager and its cached objects. You can manually set the flag to "true" if you want to use nested transactions for JDBC access code which participates in JPA transactions (provided that your JDBC driver supports Savepoints). Note that JPA itself does not support nested transactions! Hence, do not expect JPA access code to semantically participate in a nested transaction.
This is very confusing but I think what it's saying is that if you set the flag mentioned, you will be able to use nested transactions, implemented using a feature of JDBC 3.0. So make sure that your driver meets that specification.
回答2:
There is still an exception thrown from the second method. So, yes, you created 2 transactions but both will be rolled back because of the same exception. If you do not want to roll back the first method then you will have to surround the call to the second method with a try catch (so that no exception is thrown to the Transactional pointcut).
In general however it is not a good idea to call one transaction from another since it will not be clear what the scope of a transaction is (this is not visible on the interface). So, if possible, better to call the 2 methods separately from your front end (or wherever from) and ignore any exception from the second method there.
UPDATE
The problem in your case however is something completely different. Spring transactions use proxies that handle the transaction (and exceptions). Since you call second() from first() you are completely bypassing the proxy so no new transaction is never created.
回答3:
try to catch second
exception on first
:
@Override
public void first() {
entityManager.persist(new TableTest1().setName("data1"));
try {
this.second();
} catch (RuntimeException e) {
// Do nothing
}
}
public void second() {
entityManager.persist(new TableTest1().setName("data2"));
throw new RuntimeException(); // it roll backs data2
}
来源:https://stackoverflow.com/questions/26467006/how-not-to-roll-back-previous-methods-persist-data