Слушатель при запуске транзакции

Я ищу чистое решение, чтобы иметь слушателя для начала транзакции. Это означает, что я хотел бы, чтобы слушатель был компонентом (компонентом) в контексте Spring, который получал бы событие при запуске транзакции от TransactionPlatformManager или Hibernate Session или чего-то подобного в точке, где начинается новая транзакция.

Что-то вместе:

@Component
class TransactionListener implements ?? {

    @Autowired
    private Something x;

    public void onTransactionBegin(...) {
        x.doSomething()
    }

}

Чтобы быть конкретным, я смягчаю общесистемную проблему, и мне нужно установить локальный поток при запуске транзакции, чтобы я мог получить доступ к этому локальному потоку в дальнейшем при обработке объектов гибернации для получения информации.

Я изучил источники и не нашел никаких следов для такого слушателя, чтобы быть достижимым. Единственное решение, которое я нашел, состояло в том, чтобы создать подкласс HibernateTransactionManager и его метод doBegin(), что я не нахожу особенно приятным.

2 ответа

У меня была похожая проблема, когда я хотел записать идентификатор сеанса Oracle, как только транзакция началась, чтобы исследовать некоторые проблемы, которые у нас есть.

В конце концов я понял, что, поскольку Spring использует PlatformTransactionManagerВы можете получить доступ ко всей информации, настроив ее.

Первое, что нужно сделать, это определить, какую реализацию вы используете. В нашем случае это было просто JpaTransactionManager объявлен в @Configuration класс, так что это было довольно легко.

Сделав это, обратите внимание, что вы можете включить отладку или трассировку для этого класса, который уже предоставляет много информации о состоянии транзакции, если ваша цель - отладить проблему.

Если этого недостаточно, его легко создать подкласс и заменить предыдущим. Затем просто переопределите методы, которые вы хотите перехватить, как doBegin() или же prepareSynchronization(),

Посмотрите, например, мою реализацию:

@Slf4j
public class LoggingJpaTransactionManager extends JpaTransactionManager {
    @Autowired
    private EntityManager entityManager;

    LoggingJpaTransactionManager(EntityManagerFactory emf) {
        super(emf);
    }

    @Override
    protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
        super.prepareSynchronization(status, definition);
        if (status.isNewTransaction() && log.isInfoEnabled()) {
            Query query = entityManager.createNativeQuery("select sys_context('USERENV','SID') from dual");
            Object sessionId = query.getSingleResult();
            log.info("Started a new transaction on session id {}", sessionId);
            TransactionSynchronizationManager.registerSynchronization(…);
        }
    }
}

Примечание: я решил переопределить prepareSynchronization() вместо doBegin() потому что это позволяет использовать TransactionSynchronizationManager, который, я думаю, остается чище, чтобы получать уведомления о событиях коммит / откат. Этот метод вызывается сразу после doBegin() тем не мение.

Spring имеет некоторые обратные вызовы транзакций в своей TransactionSynchronization, однако, как вы правильно заметили, обратного вызова для запуска транзакции нет, моя ошибка.

Насколько я знаю, Spring не сообщит, когда начнутся транзакции, хотя это может отличаться в разных реализациях. PlatformTransactionManager, Если вы хотите подключиться к транзакции Spring, я думаю, что вы остались с

  1. Подкласса менеджера транзакций и вызова некоторого обратного вызова
  2. Создать совет для @Transactional с Spring-AOP (это будет работать только, если вы используете аннотации, очевидно)

Если вы используете Hibernate, вам может повезти с afterTransactionBegin в https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Interceptor.html

Это работает для меня до сих пор.

@Aspect
@Component
public class StartTransactionInterceptor {

    @Pointcut("target(org.springframework.transaction.PlatformTransactionManager)")
    public void isPlatformTransactionManager() {
    }

    @Pointcut("execution(org.springframework.transaction.TransactionStatus getTransaction("
            + "org.springframework.transaction.TransactionDefinition)))")
    public void getsTransaction() {
    }

    @Around("isPlatformTransactionManager() && getsTransaction()")
    public Object registerSynchronization(ProceedingJoinPoint joinPoint) throws Throwable {
        TransactionStatus value = (TransactionStatus)joinPoint.proceed();
        if (value.isNewTransaction()) {
            // send some application event to others who are interested
        }
        return value;
    }
}

Кроме того, вы можете использовать Spring's SimpleTransactionScope и область действия bean для области действия транзакции. Боб будет лениво создаваться, как другие зовут DealWithStuffPerTx.addMoreStuff(Object) ниже в рамках транзакции.

@Configuration
public class TransactionScopeConfig implements BeanFactoryPostProcessor {

    public static final String NAME = "tx";
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope(NAME, new SimpleTransactionScope());
    }
}

@Component
@Scope(value = TransactionScopeConfig.NAME, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DealWithStuffPerTx extends TransactionSynchronizationAdapter {

    public void addMoreStuff(Object stuff) {
    }

    @Override
    public void afterCommit() {
        // deal with stuff
    }

    @PostConstruct
    public void init() {
        TransactionSynchronizationManager.registerSynchronization(this);
    }
}
Другие вопросы по тегам