【问题标题】:Mixing declarative and programmatic transactions with Spring and JPA listeners将声明式和编程式事务与 Spring 和 JPA 侦听器混合
【发布时间】:2011-09-14 19:43:00
【问题描述】:

我正在使用 JPA EntityListener 执行一些额外的审计工作,并使用 @Configurable 将 Spring 管理的 AuditService 注入到我的 AuditEntryListener 中。 AuditService 生成一组 AuditEntry 对象。 AuditService 本身是一个单例范围的 bean,我想将所有 AuditEntry 对象收集在一个公共键下,然后可以由最外层的服务层访问(调用持久调用的那个又触发了 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 实体监听器

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

}

审计服务

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

}

【问题讨论】:

    标签: java spring jpa transactions synchronization


    【解决方案1】:

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

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

    您可以在following library for Postgresql 中看到使用 Spring 托管事务添加自定义事务和挂钩的模式

    【讨论】:

      【解决方案2】:

      @菲利普

      据我了解,您的要求是:

      1. 在每个事务中生成一个唯一的令牌(数据库 当然是交易)
      2. 让这个独特的令牌在所有层都易于访问

      因此,您自然会考虑 Spring 提供的 TransactionSynchronizationManager 作为存储唯一令牌(在本例中为 UID)的工具

      使用这种方法要非常小心,TransactionSynchronizationManager 是管理 Spring 的所有 @Transactional 处理的主要存储助手。在@Transactional 引擎下,Spring 正在创建一个适当的 EntityManager、一个适当的同步对象,并使用 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拦截这些方法调用,为你管理Thread本地处理

        例子:

      修改审计服务

      @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())...
          ...
      }
      

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-21
        • 1970-01-01
        相关资源
        最近更新 更多