лучший подход для вызова 2 сервисных методов из планировщика в одной транзакции - PullRequest
0 голосов
/ 12 марта 2019

У меня есть расписание:

@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) Добавить @Transactional выше private void process() и изменить этот метод на public

2) Добавить @Transactional выше public void process(User user) методв UserService

В первом случае это не помогает, потому что process() witn @Transactional вызывает из того же класса.

Во втором случае это помогает.

Но если я добавлю новую Службу, например 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 и вызвать logService.save в userService.process метод

2) Создайте новую службу, например SchedulerService с методом process, и введите userService и logService в эту службу.И звоните в оба сервиса за одну транзакцию.

В первом случае я получаю новую зависимость в userService, и это может нарушить зону ответственности в этом сервисе.Почему служба должна знать, что нужно тянуть другую службу

Во втором случае мне нужно создать дополнительную службу (еще один класс)

Было бы идеально, если бы была возможность комментировать внутренний метод Schedulers @Transactionalаннотаций.Я знаю, что это можно сделать, используя cglib вместо прокси , но я использую прокси.

Какой подход будет лучше?

1 Ответ

0 голосов
/ 12 марта 2019

Имхо, это хороший вариант использования для PlatformTransactionManager, с или без TransactionTemplate.
Для этого я собираюсь с чистым PlatformTransactionManager решением.

Если вы используете Spring Boot, то по умолчанию оно будет иметь Боб.

@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 "устранит" необходимость явного вызова commit и rollback.

Вы можете иметь TransactionTemplate в качестве Бина, или вы можете создать его вручную из 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()));
    }
});
...