当前位置: 首页 > 知识库问答 >
问题:

多线程中的Spring事务管理?

澹台欣怿
2023-03-14

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

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

我的预期结果是“开始,1,3,5,结束”。

而实际结果是‘开始,1,2,3,4,5,结束’。

欢迎任何回复!谢谢!

主要类别:

@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();

            }

        }

    }

}

共有2个答案

祁博涛
2023-03-14

在参考@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')");

                    }

                }

            });

        }

    }

}

结果是“开始,1,3,5,结束”。

姚胡媚
2023-03-14

你的代码有几件事,因为你试图超越Spring和Spring Boot。与其试图这样做,不如使用框架而不是围绕它们。

  1. 抛弃你的@Configuration类,让Spring Boot进行配置
  2. 使用事务模板,而不是自己搞砸(错误!)连接
  3. 使用默认配置的Spring TaskExecutor,而不是手动访问执行器

将其添加到<code>应用程序中。属性

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

使用TransactionTemboard代替乱搞连接。

@@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");
        };
    }
}

类似上面的内容会产生您想要的结果。请注意,您现在使用框架提供的< code>JdbcTemplate 、< code>TransactionTemplate和< code>TaskExecutor。

 类似资料:
  • 问题内容: 我正在使用Callable接口在serviceImpl中编写多线程程序。我正在使用spring事务管理器。在DB中执行更新操作时,它会成功执行。但是更新后的数据不会反映在DB中。但是,当我运行不带多线程的程序时,它将在DB中更新。 这是我的配置 我可以转向事务管理器的另一种方法。只是我想确认这种方法是否支持多线程。所以我的问题是 spring事务管理器是否支持多线程(我的意思是仅通过声

  • 我正在使用Spring AMQP(RabbitMQ实现),我试图将单个事务传播到多个线程中。 例如,假设有3个队列,名称为X、Y、Z,首先我使用thread-1从队列X获取一条消息,然后,该消息被提供给thread-0,thread-0中的消息被克隆并通过thread-3发送到队列Y、thread-2和队列Z。线程0等待线程3和线程4的完成,以提交或回滚消息。注意,这里我使用了4个线程。 我想要的

  • 我需要执行父任务,父任务可能有子任务,也可能没有子任务。每个父任务和子任务都应该在线程中运行。如果父任务或子任务执行中出现错误,则必须回滚父任务和子任务的事务。我正在使用hibernate4。

  • 我在批处理作业中使用多线程步骤来处理来自源数据库的记录并写入目标数据库。该步骤基于块,由JdbcpagingItemReader、Processor和JDBCBathItemWriter组成。我明白,如果在步骤处理期间发生任何异常,数据库事务将回滚整个块。我想了解一下Spring batch在内部是如何管理的?由于这是多线程步骤,因此不能保证处理器和写入器在块的同一线程中执行。块可能由不同的线程处

  • 问题内容: 我有如下方法: methodB可以正常工作吗?根据我的理解,methodB将附加methodA的事务,如果methodA在methodB之前退出该怎么办?我想事务只能提交methodA。但是methodB将不会提交,因为该事务之前已提交。 我可以对方法B使用@Transactional(propagation = Propagation.REQUIRES_NEW)。这可以使method

  • 我试图理解为什么下面的代码片段不能像预期的那样工作。 我已经有的客户,我正在创建2个可调用的任务,并要求他们执行方法。 这两个事务一起运行(在从数据库中读取客户副本(如代码所示)后,我引入了1秒的延迟),并使用