Spring 事务 - 如果任何人失败,如何允许完成相关事务
Spring Transaction - How to allow dependent transactions to be complted if anyone fails
我正在使用 "Spring TX"
v 4.3.2.RELEASE
也是新手。在我的应用程序中,有一些依赖任务,比如在单个事务中,我想将数据插入 customer
和 address
而不管是否存在异常。
应该保存数据。这里我不需要关心Consistency
的数据。我只是在我的服务 class 上使用了 @Transactional
注释,但在这种情况下,如果在保存 address
的数据时出现问题,我希望 Customer
数据应该被保存。我尝试了以下选项,但没有成功。
@Transactional(propagation = Propagation.REQUIRES_NEW)
。请指导我们如何做到这一点?
以下代码供参考:
CustomerService.java
public interface CustomerService {
public void createCustomer(Customer cust);
}
CustomerServiceImpl.java
public class CustomerServiceImpl implements CustomerService {
private CustomerDAO customerDAO;
public void setCustomerDAO(CustomerDAO customerDAO) {
this.customerDAO = customerDAO;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createCustomer(Customer cust) {
customerDAO.create(cust);
}
}
CustomerDAO.java
public interface CustomerDAO {
public void create(Customer customer);
}
CustomerDAOImpl.java
public class CustomerDAOImpl implements CustomerDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void create(Customer customer) {
String queryCustomer = "insert into Customer (id, name) values (?,?)";
String queryAddress = "insert into Address (id, address,country) values (?,?,?)";
jdbcTemplate.update(queryCustomer, new Object[] { customer.getId(), customer.getName() });
System.out.println("Inserted into Customer Table Successfully");
jdbcTemplate.update(queryAddress, new Object[] { customer.getId(), customer.getAddress().getAddress(),customer.getAddress().getCountry() });
System.out.println("Inserted into Address Table Successfully");
}
}
spring-context.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:database.properties" />
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
<!-- Creating TransactionManager Bean, since JDBC we are creating of type DataSourceTransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- MySQL DB DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${mysql.driver.class.name}" />
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</bean>
<bean id="customerDAO" class="com.journaldev.spring.jdbc.dao.CustomerDAOImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="customerManager" class="com.journaldev.spring.jdbc.service.CustomerServiceImpl">
<property name="customerDAO" ref="customerDAO"></property>
</bean>
</beans>
db.sql
CREATE TABLE `Customer` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Address` (
`id` int(11) unsigned NOT NULL,
`address` varchar(20) DEFAULT NULL,
`country` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
TransactionManagerMain.java
public class TransactionManagerMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
CustomerService customerManager = ctx.getBean("customerManager", CustomerServiceImpl.class);
Customer cust = createDummyCustomer();
customerManager.createCustomer(cust);
ctx.close();
}
private static Customer createDummyCustomer() {
Customer customer = new Customer();
customer.setId(2);
customer.setName("Pankaj");
Address address = new Address();
address.setId(2);
address.setCountry("India");
// setting value more than 20 chars, so that SQLException occurs
address.setAddress("Albany Dr, San Jose, CA 95129");
customer.setAddress(address);
return customer;
}
}
如果您需要任何其他信息,请告诉我。
Although I get below error, I expect 1st transaction should get committed.
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into Address (id, address,country) values (?,?,?)]; Data truncation: Data too long for column 'address' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:102)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:870)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:931)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:941)
at com.journaldev.spring.jdbc.dao.CustomerDAOImpl.create(CustomerDAOImpl.java:25)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl.createCustomer(CustomerServiceImpl.java:20)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl$$FastClassBySpringCGLIB$$faf0749.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl$$EnhancerBySpringCGLIB$dab5ce1.createCustomer(<generated>)
at com.journaldev.spring.jdbc.main.TransactionManagerMain.main(TransactionManagerMain.java:18)
Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3249)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440)
at org.springframework.jdbc.core.JdbcTemplate.doInPreparedStatement(JdbcTemplate.java:877)
at org.springframework.jdbc.core.JdbcTemplate.doInPreparedStatement(JdbcTemplate.java:870)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633)
... 16 more
首先,您的错误与交易处理无关。阅读其消息:
Data truncation: Data too long for column 'address'
因此,"Albany Dr, San Jose, CA 95129"
太长,无法放入地址栏。
关于您的问题,您使用单个事务来保存客户和地址,因此如果抛出异常,所有内容都将回滚:这就是事务的定义。如果希望即使地址插入失败也能提交客户,则需要两个事务:一个保存客户并提交,第二个保存地址并提交。
为了让它们相互独立,你需要将它们放在不同的事务中。你需要用 Dao 做这样的事情 class:
public class CustomerDAOImpl implements CustomerDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createCustomer(Customer customer) {
String queryCustomer = "insert into Customer (id, name) values (?,?)";
jdbcTemplate.update(queryCustomer, new Object[] { customer.getId(), customer.getName() });
System.out.println("Inserted into Customer Table Successfully");
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createAddress(Customer customer) {
String queryAddress = "insert into Address (id, address,country) values (?,?,?)";
jdbcTemplate.update(queryAddress, new Object[] { customer.getId(), customer.getAddress().getAddress(),customer.getAddress().getCountry() });
System.out.println("Inserted into Address Table Successfully");
}
}
并从服务中删除事务注释 class。有了这个,您将有两个事务从 Dao 层开始,每个事务都针对 Customer 和 Address,并且一个的任何错误都不会回滚另一个。
我正在使用 "Spring TX"
v 4.3.2.RELEASE
也是新手。在我的应用程序中,有一些依赖任务,比如在单个事务中,我想将数据插入 customer
和 address
而不管是否存在异常。
应该保存数据。这里我不需要关心Consistency
的数据。我只是在我的服务 class 上使用了 @Transactional
注释,但在这种情况下,如果在保存 address
的数据时出现问题,我希望 Customer
数据应该被保存。我尝试了以下选项,但没有成功。
@Transactional(propagation = Propagation.REQUIRES_NEW)
。请指导我们如何做到这一点?
以下代码供参考: CustomerService.java
public interface CustomerService {
public void createCustomer(Customer cust);
}
CustomerServiceImpl.java
public class CustomerServiceImpl implements CustomerService {
private CustomerDAO customerDAO;
public void setCustomerDAO(CustomerDAO customerDAO) {
this.customerDAO = customerDAO;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createCustomer(Customer cust) {
customerDAO.create(cust);
}
}
CustomerDAO.java
public interface CustomerDAO {
public void create(Customer customer);
}
CustomerDAOImpl.java
public class CustomerDAOImpl implements CustomerDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void create(Customer customer) {
String queryCustomer = "insert into Customer (id, name) values (?,?)";
String queryAddress = "insert into Address (id, address,country) values (?,?,?)";
jdbcTemplate.update(queryCustomer, new Object[] { customer.getId(), customer.getName() });
System.out.println("Inserted into Customer Table Successfully");
jdbcTemplate.update(queryAddress, new Object[] { customer.getId(), customer.getAddress().getAddress(),customer.getAddress().getCountry() });
System.out.println("Inserted into Address Table Successfully");
}
}
spring-context.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:database.properties" />
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
<!-- Creating TransactionManager Bean, since JDBC we are creating of type DataSourceTransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- MySQL DB DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${mysql.driver.class.name}" />
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</bean>
<bean id="customerDAO" class="com.journaldev.spring.jdbc.dao.CustomerDAOImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="customerManager" class="com.journaldev.spring.jdbc.service.CustomerServiceImpl">
<property name="customerDAO" ref="customerDAO"></property>
</bean>
</beans>
db.sql
CREATE TABLE `Customer` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Address` (
`id` int(11) unsigned NOT NULL,
`address` varchar(20) DEFAULT NULL,
`country` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
TransactionManagerMain.java
public class TransactionManagerMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
CustomerService customerManager = ctx.getBean("customerManager", CustomerServiceImpl.class);
Customer cust = createDummyCustomer();
customerManager.createCustomer(cust);
ctx.close();
}
private static Customer createDummyCustomer() {
Customer customer = new Customer();
customer.setId(2);
customer.setName("Pankaj");
Address address = new Address();
address.setId(2);
address.setCountry("India");
// setting value more than 20 chars, so that SQLException occurs
address.setAddress("Albany Dr, San Jose, CA 95129");
customer.setAddress(address);
return customer;
}
}
如果您需要任何其他信息,请告诉我。
Although I get below error, I expect 1st transaction should get committed.
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into Address (id, address,country) values (?,?,?)]; Data truncation: Data too long for column 'address' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:102)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:870)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:931)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:941)
at com.journaldev.spring.jdbc.dao.CustomerDAOImpl.create(CustomerDAOImpl.java:25)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl.createCustomer(CustomerServiceImpl.java:20)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl$$FastClassBySpringCGLIB$$faf0749.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
at com.journaldev.spring.jdbc.service.CustomerServiceImpl$$EnhancerBySpringCGLIB$dab5ce1.createCustomer(<generated>)
at com.journaldev.spring.jdbc.main.TransactionManagerMain.main(TransactionManagerMain.java:18)
Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3249)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440)
at org.springframework.jdbc.core.JdbcTemplate.doInPreparedStatement(JdbcTemplate.java:877)
at org.springframework.jdbc.core.JdbcTemplate.doInPreparedStatement(JdbcTemplate.java:870)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633)
... 16 more
首先,您的错误与交易处理无关。阅读其消息:
Data truncation: Data too long for column 'address'
因此,"Albany Dr, San Jose, CA 95129"
太长,无法放入地址栏。
关于您的问题,您使用单个事务来保存客户和地址,因此如果抛出异常,所有内容都将回滚:这就是事务的定义。如果希望即使地址插入失败也能提交客户,则需要两个事务:一个保存客户并提交,第二个保存地址并提交。
为了让它们相互独立,你需要将它们放在不同的事务中。你需要用 Dao 做这样的事情 class:
public class CustomerDAOImpl implements CustomerDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createCustomer(Customer customer) {
String queryCustomer = "insert into Customer (id, name) values (?,?)";
jdbcTemplate.update(queryCustomer, new Object[] { customer.getId(), customer.getName() });
System.out.println("Inserted into Customer Table Successfully");
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createAddress(Customer customer) {
String queryAddress = "insert into Address (id, address,country) values (?,?,?)";
jdbcTemplate.update(queryAddress, new Object[] { customer.getId(), customer.getAddress().getAddress(),customer.getAddress().getCountry() });
System.out.println("Inserted into Address Table Successfully");
}
}
并从服务中删除事务注释 class。有了这个,您将有两个事务从 Dao 层开始,每个事务都针对 Customer 和 Address,并且一个的任何错误都不会回滚另一个。