【问题标题】:best approach to call 2 services methods from scheduler in one transaction在一个事务中从调度程序调用 2 个服务方法的最佳方法
【发布时间】:2023-04-02 16:30:01
【问题描述】:

我有调度器:

@Component
public class MyScheduler {

    private static final long INIT_DELAY = 1L;
    private static final long DELAY = 10L;

    private final UserService userService;

    public MyScheduler(UserService userService) {
        this.userService = userService;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void schedule() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
    }

    private void process() {
        userService.process(new User("Bill", 20));
    }
}

在 UserService 中我保存新用户并抛出异常:

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void process(User user) {
        log.info("Start process...");
        userRepository.save(user);
        methodWithException();
        log.info("End process...");
    }

    private void methodWithException() {
        throw new RuntimeException();
    }
}

因此,尽管出现异常,用户仍被保存。为了解决这个问题,我可以应用几种方法:

1) 在private void process()上方添加@Transactional,并将此方法改为public

2) 在UserServicepublic void process(User user)方法上方添加@Transactional

在第一种情况下它没有帮助,因为 process() witn @Transactional 从同一个类调用。

在第二种情况下它会有所帮助。

但是如果我添加新的Service,例如LogService:

@Service
public class LogServiceImpl implements LogService {

    private final LogRepository logRepository;

    public LogServiceImpl(LogRepository logRepository) {
        this.logRepository = logRepository;
    }

    @Transactional
    @Override
    public Log save(Log log) {
        return logRepository.save(log);
    }
}

并将调度程序更改为:

@Component
public class MyScheduler {

    private static final long INIT_DELAY = 1L;
    private static final long DELAY = 10L;

    private final UserService userService;
    private final LogService logService;

    public MyScheduler(UserService userService, LogService logService) {
        this.userService = userService;
        this.logService = logService;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void schedule() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
    }

    private void process() {
        User user = userService.process(new User("Bill", 20));
        logService.save(new Log(user.getId(), new Date()));
    }
}

问题

userService.process 调用一个事务,logService.save 调用另一个事务。我需要在一笔交易中调用双方服务。

我看到了两种方式:

1) 将logService 注入userService 并在userService.process 方法中调用logService.save

2) 使用process 方法创建新服务,例如SchedulerService,并在该服务中注入userServicelogService。并在一个事务中调用两个服务。

在第一种情况下,我在userService 中获得了新的依赖项,这可能违反了此服务的责任范围。为什么服务应该知道拉另一个服务

在第二种情况下,我需要创建额外的服务(一个类)

最好能够注释内部调度程序方法@Transactional 注释。我知道这可以使用 cglib 而不是 proxy 来完成,但我使用代理。

哪种方法更好?

【问题讨论】:

  • cglib 也是一个代理。区别一个是基于接口的另一个是基于类的,但仍然都是代理。在process 方法中使用TransactionTemplate 将这两种方法包装在一个事务中。或者将process 方法移动到不同的类,然后简单地将方法标记为@Transactional
  • @ M. Deinum “或者将流程方法移动到不同的类并简单地将方法标记为@Transactional” - 这只是我的第二种情况 - “2)创建新服务,例如带有方法的 SchedulerService在此服务中处理并注入 userService 和 logService。并在一个事务中调用两个服务。"。

标签: java spring design-patterns transactions scheduled-tasks


【解决方案1】:

恕我直言,这是 PlatformTransactionManager 的一个很好的用例,有或没有 TransactionTemplate
为此,我将使用纯 PlatformTransactionManager 解决方案。

如果您使用的是Spring Boot,默认情况下您会将其作为 Bean。

@Component
class MyScheduler {
    private static final long INIT_DELAY = 1L;
    private static final long DELAY = 10L;

    private final PlatformTransactionManager txManager;
    private final ConcurrencyService userService;
    private final LogService logService;

    MyScheduler(
            final PlatformTransactionManager txManager,
            final ConcurrencyService userService,
            final LogService logService) {
        this.txManager = txManager;
        this.userService = userService;
        this.logService = logService;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void schedule() {
        final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(this::process, INIT_DELAY, DELAY, TimeUnit.SECONDS);
    }

    private void process() {
       final DefaultTransactionDefinition definition = new DefaultTransactionDefinition(PROPAGATION_REQUIRES_NEW);
       final TransactionStatus tx = txManager.getTransaction(definition);

       try {
          final User user = userService.process(new User("Bill", 20));
          logService.save(new Log(user.getId(), new Date()));
          txManager.commit(tx);
       } catch (final YourException e) {
          txManager.rollback(tx);
       }
    }
}

使用TransactionTemplate 将“消除”显式调用commitrollback 的需要。

您可以将TransactionTemplate 作为一个Bean,或者您可以从PlatformTransactionManager 手动构造它,就像我在这里所做的那样。

final TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(final TransactionStatus status) {
       final User user = userService.process(new User("Bill", 20));
       logService.save(new Log(user.getId(), new Date()));
    }
});

【讨论】:

  • 不要。而是使用TransactionTemplate 将这两种方法包装在一个事务中。节省手动执行所有提交/回滚等的开销。
  • @M.Deinum 我完全不同意。我更喜欢在提交或回滚时明确查看。但我想这是一个品味问题。这就是为什么我写有/没有。
  • 我们可以同意不同意,但 Spring 团队也建议在考虑 PlatformTransactionManager 之前使用 TransactionTemplate
  • @M.Deinum 我将添加一个示例。
  • @ M. Deinum 明确地使用可以与方面一起使用的东西总是让我害怕。你对我的选择有什么看法? (为调度程序创建一个单独的服务,这两个服务都将被称为服务)。现在我有了这样的实现——一个服务注入另一个服务。并在其方法(在事务中)结束时调用此服务的方法。我想重写它,以便为调度程序提供一个单独的服务,并在其中将两个服务的调用封装在一个单独的事务中。因此问了一个问题。
猜你喜欢
  • 1970-01-01
  • 2012-12-24
  • 1970-01-01
  • 1970-01-01
  • 2021-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-29
相关资源
最近更新 更多