Spring 事务 - 如果任何人失败,如何允许完成相关事务

Spring Transaction - How to allow dependent transactions to be complted if anyone fails

我正在使用 "Spring TX" v 4.3.2.RELEASE 也是新手。在我的应用程序中,有一些依赖任务,比如在单个事务中,我想将数据插入 customeraddress 而不管是否存在异常。

应该保存数据。这里我不需要关心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,并且一个的任何错误都不会回滚另一个。