将声明式和程序式事务与Spring和JPA侦听器混合

时间:2011-06-14 15:28:43

标签: java spring jpa transactions synchronization

我正在使用JPA EntityListener进行一些额外的审计工作,并使用@Configurable将Spring管理的AuditService注入到AuditEntryListener中。 AuditService生成AuditEntry对象的集合。 AuditService本身就是一个Singleton范围的bean,我想收集一个公共密钥下的所有AuditEntry对象,然后可以由最外层的服务层(调用persist调用,然后触发EntityListener的那个)访问它。

我正在考虑使用Spring的TransactionSynchronizationManager在事务开始时设置一个特定的事务名称(使用UID()或其他一些独特的策略),然后使用该名称作为AuditService中的一个键,这将允许我对该事务中创建的所有AuditEntry对象进行分组。

混合声明式和程序式事务管理是否有可能出现问题? (虽然我只是设置事务名称)。有没有更好的方法将生成的AuditEntry对象与当前事务相关联?这个解决方案对我有用,但鉴于TransactionSynchronizationManager不适合应用程序使用,我想确保我对它的使用不会导致一些不可预见的问题。

相关问题

最后,一个相关的,但不是直接相关的问题:我知道JPA EntityListeners的文档警告不要使用当前的EntityManager,但是如果我确实想用它来区分对象的持久化自我,我会安全吗在我的preUpdate()方法周围使用@Transactional(propagation = REQUIRES_NEW)注释?

原型代码:

服务类

@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 个答案:

答案 0 :(得分:3)

@Filip

据我了解,您的要求是:

  1. 在每个事务(数据库)中生成唯一标记 当然是交易)
  2. 在所有图层中轻松访问此唯一令牌
  3. 很自然地,您正在考虑Spring提供的TransactionSynchronizationManager作为存储唯一令牌的工具(在本例中为UID)

    要非常小心这种方法,TransactionSynchronizationManager是管理Spring的所有@Transactional处理的主要存储助手。在@Transactional引擎下,Spring正在创建一个合适的EntityManager,一个适当的Synchronization对象,并使用TransactionSynchronizationManager将它们附加到本地线程。

    在您的服务类代码中,在@Transactional方法中,您正在篡改同步对象,它可能会导致不良行为。

    我已经对@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拦截这些方法调用并为您管理Thread本地处理

      示例:

    3. 修改过的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部分

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

      希望这会有所帮助。

答案 1 :(得分:2)

您可以使用TransactionSynchronizationManager提供解决方案。我们使用JPA注册“TransactionInterceptorEntityListener”作为实体监听器。我们想要实现的是能够监听CRUD事件,以便我们可以使用弹簧管理的“监听器”,该监听器具有与当前事务相关联的生命周期(即,弹簧管理但每个事务的实例)。我们对JPATransactionManager进行子类化,并在prepareSynchronization()方法中引入一个用于设置“TransactionInterceptorSynchronizer”的钩子。我们还使用相同的钩子来允许代码(在程序tx中)与当前事务关联和检索任意对象,并且还注册在事务提交之前/之后运行的作业。

整体代码很复杂,但绝对可行。如果您使用JPATemplates进行编程tx,很难实现这一点。所以我们推出了自己的模板,在处理拦截器工作之后简单地调用JPA模板。我们计划很快开源我们的JPA库(在Spring的类之上编写)。

您可以在following library for Postgresql

中看到添加自定义事务和挂钩与Spring托管交易的模式