在一个事务中从调度程序调用 2 个服务方法的最佳方法



I have scheduller:

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

在用户服务中,我保存新用户并抛出异常:

@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)在UserService中添加@Transactionalpublic void process(User user)方法

上方在第一种情况下,它没有帮助,因为process() witn @Transactional来自同一类的调用。

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

但是如果我添加新服务,例如日志服务:

@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.savecal。我需要在一个事务中调用 bouth 服务。

我看到两种方式:

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

2) 创建新服务,例如使用方法processSchedulerService,并在此服务中注入userServicelogService。并在一笔交易中调用bouth服务。

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

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

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

哪种方法会更好?

恕我直言,这是一个很好的用例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()));
}
});

最新更新