【问题标题】:Spring Transaction Management in Multi-Thread?多线程中的Spring事务管理?
【发布时间】:2020-09-11 20:04:27
【问题描述】:

Spring Transaction 不支持多线程,所以我尝试在 Thread 的 run() 方法中手动管理事务。但是,它不起作用!

我想在下面的示例中回滚每个线程的 run() 方法,当其中有异常抛出时。 (在以下情况下,INSERT INTO UNKNOWN_TABLE)

我的预期结果是 'start, 1, 3, 5, end'。

而实际结果是'start, 1, 2, 3, 4, 5, end'。

欢迎回复!谢谢!


主类:

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    private TestService testService;

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);

    }

    @Bean
    public DriverManagerDataSource createDriverManagerDataSource() {

        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        dataSource.setJdbcUrl("jdbc:oracle:thin:@url:port/schema");
        dataSource.setUsername("xxxx");
        dataSource.setPassword("xxxx");

        return dataSource;

    }

    @Bean
    public JdbcTemplate createJdbcTemplate() {

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(createDriverManagerDataSource());

        return jdbcTemplate;

    }

    @Override
    public void run(String... args) throws Exception {

        testService.test();

    }

}

服务类:

@Service
public class TestService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Exception.class)
    public void test() throws Exception {

        jdbcTemplate.batchUpdate("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('start', 'start')");

        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 1; i <= 5; i++) {

            executorService.submit(new TestRunner(i));

        }

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

        jdbcTemplate.batchUpdate("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('end', 'end')");
    }

    private class TestRunner implements Runnable {

        private Integer id;

        public TestRunner(Integer id) {

            this.id = id;

        }

        @Override
        public void run() {

            try (Connection connection = jdbcTemplate.getDataSource().getConnection()) {

                try {

                    connection.setAutoCommit(false);

                    String sqlString = String.format("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('%d', '%d')", id, id);
                    jdbcTemplate.batchUpdate(sqlString);

                    if (id % 2 == 0) {
                        // Except the transaction been rollback when this.id is 2 or 4.
                        jdbcTemplate.batchUpdate("INSERT INTO UNKNOWN_TABLE(MYKEY, MYVALUE) VALUES ('no', 'no')");

                    }

                    connection.commit();

                } catch (Exception e) {

                    System.err.println("Failure: UNKNOWN_TABLE");
                    connection.rollback();

                } finally {

                    connection.close();

                }

            } catch (SQLException e2) {

                e2.printStackTrace();

            }

        }

    }

}

【问题讨论】:

  • 当然不行。您正试图超越框架,但您实际上所做的是引入了连接泄漏(有点)。 jdbcTemplate 使用的连接与您使用的不同,因此设置自动提交是无用的。您应该做的是使用TransactionTemplate 并将代码包装在其中,而不是自己搞乱连接。此外,您使用的是自动配置数据源、jdcbtemplate 和事务模板的 Spring boot,为什么要自己做呢?
  • 感谢您告诉我 jdbcTemplate 使用的连接与我手动创建的连接不同。我没有使用 Spring Boot 自动配置,因为我是从 Spring Project 复制代码。 (我会使用 Spring Boot 自动配置和事务模板在下面发布我的答案)

标签: spring multithreading transactions


【解决方案1】:

当您试图超越 Spring 和 Spring Boot 时,您的代码需要做一些事情。与其尝试这样做,不如使用框架而不是围绕它们工作。

  1. 放弃你的 @Configuration 类,让 Spring Boot 进行配置
  2. 使用TransactionTemplate 而不是自己弄乱(错误!)Connection
  3. 使用默认配置的 Spring TaskExecutor,而不是手动访问 Executor

将此添加到您的application.properties

spring.datasource.url=jdbc:oracle:thin:@url:port/schema
spring.datasource.username=xxxx
spring.datasource.password=xxxx

使用TransactionTemplate 而不是搞乱连接。

@@SpringBootApplication
public class Application {

    private static final String SQL = "INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES (?, ?)";
    private static final String ERROR_SQL = "INSERT INTO UNKNOWN_TABLE(MYKEY, MYVALUE) VALUES (?, ?)";


    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner testRunner(JdbcTemplate jdbc, TransactionTemplate tx, TaskExecutor tasks) {
        return (args) -> {
            jdbc.update(SQL, "start", "start");
            IntStream.range(1, 6)
                    .forEach(id -> {
                        try {
                            tasks.execute(() -> tx.executeWithoutResult((s) -> {
                                jdbc.update(SQL, id, id);
                                if (id % 2 == 0) {
                                    jdbc.update(ERROR_SQL, "no", "no");
                                }
                            }));
                        } catch (DataAccessException e) {
                            e.printStackTrace();
                        }
                    });
            jdbc.update(SQL, "end", "end");
        };
    }
}

类似上面的东西会产生你想要的结果。请注意,您现在使用框架提供的JdbcTemplateTransactionTemplateTaskExecutor

【讨论】:

    【解决方案2】:

    参考@M。 Deinum 回答,我已将代码更改为以下,它满足了我的需求。


    application.properties

    spring.datasource.url=jdbc:oracle:thin:@ip:port/schema
    spring.datasource.username=xxxx
    spring.datasource.password=xxxx
    spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
    

    主类

    @SpringBootApplication
    public class Application implements CommandLineRunner {
    
        @Autowired
        private TestService testService;
    
        public static void main(String[] args) {
    
            SpringApplication.run(Application.class, args);
    
        }
    
        @Override
        public void run(String... args) throws Exception {
    
            testService.test();
            System.exit(0);
    
        }
    
    }
    

    测试服务

    @Service
    public class TestService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Autowired
        private PlatformTransactionManager transactionManager;
    
        @Transactional(rollbackFor = Exception.class)
        public void test() throws Exception {
    
            jdbcTemplate.batchUpdate("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('start', 'start')");
    
            ExecutorService executorService = Executors.newFixedThreadPool(5);
    
            for (int i = 1; i <= 5; i++) {
    
                executorService.submit(new TestRunner(i));
    
            }
    
            executorService.shutdown();
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    
            jdbcTemplate.batchUpdate("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('end', 'end')");
        }
    
        private class TestRunner implements Runnable {
    
            private Integer id;
    
            public TestRunner(Integer id) {
    
                this.id = id;
    
            }
    
            @Override
            public void run() {
    
                TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
    
                        String sqlString = String.format("INSERT INTO TB_MYTEST(MYKEY, MYVALUE) VALUES ('%d', '%d')", id, id);
                        jdbcTemplate.batchUpdate(sqlString);
    
                        if (id % 2 == 0) {
    
                            jdbcTemplate.batchUpdate("INSERT INTO UNKNOWN_TABLE(MYKEY, MYVALUE) VALUES ('no', 'no')");
    
                        }
    
                    }
    
                });
    
            }
    
        }
    
    }
    

    结果为 'start, 1, 3, 5, end'。

    【讨论】:

      猜你喜欢
      • 2013-05-26
      • 2021-02-20
      • 2018-10-24
      • 2015-09-07
      • 2019-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      相关资源
      最近更新 更多