Смешивание декларативных и программных транзакций со слушателями Spring и JPA - PullRequest
10 голосов
/ 14 июня 2011

Я использую JPA EntityListener, чтобы выполнить дополнительную работу по аудиту, и внедряю Springit AuditService в мой AuditEntryListener, используя @Configurable. AuditService создает коллекцию объектов AuditEntry. AuditService сам по себе является компонентом синглтонной области, и я хотел бы собрать все объекты AuditEntry под общим ключом, к которому затем может обращаться самый внешний сервисный уровень (тот, который вызвал постоянный вызов, который, в свою очередь, вызвал EntityListener).

Я смотрю на использование Spring TransactionSynchronizationManager для установки конкретного имени транзакции (с помощью UID () или другой уникальной стратегии) ​​в начале транзакции, а затем использую это имя в качестве ключа в AuditService, что позволит мне сгруппировать все объекты AuditEntry, созданные в этой транзакции.

Может ли смешивание декларативного и программного управления транзакциями иметь проблемы? (Хотя я ничего не делаю, кроме установки названия транзакции). Есть ли лучший способ связать сгенерированные объекты AuditEntry с текущей транзакцией? Это решение действительно работает для меня, но, учитывая, что TransactionSynchronizationManager не предназначен для использования в приложениях, я хотел бы убедиться, что его использование не вызовет некоторых непредвиденных проблем.

Смежный вопрос

Наконец, связанный, но не сразу уместный вопрос: я знаю, что документация для JPA EntityListeners предостерегает от использования текущего EntityManager, но если бы я действительно хотел использовать его для сравнения объекта с его постоянным Я, я был бы в безопасности использовать аннотацию @Transactional (распространение = REQUIRES_NEW) вокруг моего метода preUpdate ()?

Код прототипа:

Класс обслуживания

@Transactional
public void create(MyEntity e) {

    TransactionSynchronizationManager.setCurrentTransactionName(new UID().toString());
    this.em.persist(e);
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            Set<AuditEntry> entries = auditService.getAuditEntries(TransactionSynchronizationManager.getCurrentTransactionName());
            if(entries != null) {
                for(AuditEntry entry : entries) {
                   //do some stuff....
                   LOG.info(entry.toString());
                }
            }
        }
    });
}

JPA EntityListener

@Configurable
public class AuditEntryListener {

@Autowired
private AuditService service;

@PreUpdate
public void preUpdate(Object entity) {
    service.auditUpdate(TransactionSynchronizationManager.getCurrentTransactionName(), entity);
}

public void setService(AuditService service) {
    this.service = service;
}

public AuditService getService() {
    return service;
}

}

AuditService

@Service
public class AuditService {
private Map<String, Set<AuditEntry>> auditEntryMap = new HashMap<String, Set<AuditEntry>>();

public void auditUpdate(String key, Object entity) {
    // do some audit work
    // add audit entries to map
    this.auditEntryMap.get(key).add(ae);
}

}

Ответы [ 2 ]

3 голосов
/ 29 декабря 2011

@ Filip

Насколько я понимаю, ваше требование:

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

Естественно, вы думаете о TransactionSynchronizationManager, предоставляемом Spring как средство хранения уникального токена (в данном случае, UID)

Будьте очень осторожны с этим подходом, TransactionSynchronizationManager является основным помощником хранилища для управления всей обработкой @Transactional для Spring.Под капотом @Transactional Spring создает соответствующий EntityManager, соответствующий объект Synchronization и присоединяет их к локальному потоку с помощью TransactionSynchronizationManager.

В коде класса обслуживания внутри метода @Transactional вы вмешиваетесь в Synchronizationобъект, это может привести к нежелательному поведению.

Я провел тщательный анализ того, как работает @Transactional, взгляните: http://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/

Теперь вернемся к вашим потребностям.Что вы можете сделать:

  1. Добавить локальный поток в AuditService, содержащий уникальный токен при входе в метод @Transactional, и уничтожить его при выходе из метода.В рамках этого вызова метода вы можете получить доступ к уникальному токену в любом слое.Объяснение использования ThreadLocal можно найти здесь: http://doanduyhai.wordpress.com/2011/12/04/threadlocal-explained/
  2. Создайте новую аннотацию, скажем, @Auditable (uid = "AuditScenario1"), чтобы комментировать методы, которые необходимо проверять, и использовать Spring AOP дляперехватить эти вызовы методов и управлять локальной обработкой потока для вас

    Пример:

Модифицированный AuditService

@Service
public class AuditService {

public uidThreadLocal = new ThreadLocal<String>();
...
...
}

Аудиторская аннотация

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Auditable 
{
    String uid();
}

Использование @Auditable аннотации

@Auditable(uid="AuditScenario1")
@Transactional
public void myMethod()
{
   // Something 
}

Spring AOP part

@Around("execution(public * *(..)) && @annotation(auditableAnnotation)) 
public Object manageAuditToken(ProceedingJoinPoint jp, Auditable auditableAnnotation)
{
    ...
    ...
    AuditService.uidThreadLocal.set(auditableAnnotation.uid())...
    ...
}

Надеюсь, это поможет.

2 голосов
/ 15 декабря 2011

Вы можете придумать решение, используя TransactionSynchronizationManager. Мы регистрируем «TransactionInterceptorEntityListener» с JPA в качестве прослушивателя сущности. Мы хотели достичь возможности прослушивания событий CRUD, чтобы мы могли работать с «слушателем» с пружинным управлением, жизненный цикл которого привязан к текущей транзакции (т. Е. С пружинным управлением, но экземпляром на транзакцию). Мы подклассифицируем JPATransactionManager и вводим в метод prepareSynchronization () ловушку для установки «TransactionInterceptorSynchronizer». Мы также используем ту же ловушку для кода разрешения (в программной передаче), чтобы связать и извлечь произвольные объекты с текущей транзакцией, а также зарегистрировать задания, которые выполняются до / после фиксации транзакции.

Общий код сложен, но определенно выполним. Если вы используете JPATemplates для программной передачи, это сложно сделать. Поэтому мы развернули наш собственный шаблон, который просто вызывает шаблон JPA после того, как позаботился о работе перехватчика. Мы планируем открыть исходный код нашей библиотеки JPA (написанной поверх классов Spring).

Шаблон добавления пользовательских транзакций и хуков с управляемыми транзакциями Spring можно увидеть в следующей библиотеке для Postgresql

...