使用Spring和JPA侦听器混合声明式和程序性事务

问题描述

|| 我正在使用JPA EntityListener进行其他一些审计工作,并使用@Configurable将Spring托管的AuditService注入到我的AuditEntryListener中。 AuditService生成AuditEntry对象的集合。 AuditService本身是一个Singleton范围的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 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;
}
} 审计服务
@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);
}

}
    

解决方法

        @菲利普 据我了解,您的要求是: 在每个交易中都有一个唯一的令牌生成(数据库 当然交易) 使此唯一令牌易于在所有层中访问 因此,您自然而然地考虑将Spring提供的TransactionSynchronizationManager用作存储唯一令牌(在本例中为UID)的工具 使用这种方法要非常小心,TransactionSynchronizationManager是管理Spring的所有@Transactional处理的主要存储助手。在@Transactional框架下,Spring正在创建一个适当的EntityManager,一个适当的Synchronization对象,并使用TransactionSynchronizationManager将它们附加到本地线程。 在服务类代码中,在@Transactional方法内部,您正在篡改Synchronization对象,它最终可能会导致不良行为。 我对@Transactional的工作原理进行了深入的分析,请看:http://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/ 现在回到您的需求。您可以做的是: 在AuditService的本地添加一个线程,该线程在进入@Transactional方法时包含唯一令牌,并在退出该方法时销毁它。在此方法调用中,您可以访问任何层中的唯一令牌。 ThreadLocal用法的说明可以在这里找到:http://doanduyhai.wordpress.com/2011/12/04/threadlocal-explained/ 创建一个新的注释,假设@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 
}
春季AOP部分
@Around(\"execution(public * *(..)) && @annotation(auditableAnnotation)) 
public Object manageAuditToken(ProceedingJoinPoint jp,Auditable auditableAnnotation)
{
    ...
    ...
    AuditService.uidThreadLocal.set(auditableAnnotation.uid())...
    ...
}
希望这会有所帮助。     ,您可以使用TransactionSynchronizationManager提出解决方案。我们向JPA注册了一个“ TransactionInterceptorEntityListener \”作为实体监听器。我们想要实现的是侦听CRUD事件的能力,以便我们可以使用具有与当前事务相关联的生命周期的Spring管理的“ listener”(即Spring管理的,但每个事务实例)。我们对JPATransactionManager进行了子类化,并在prepareSynchronization()方法中引入了一个用于设置\“ TransactionInterceptorSynchronizer \”的钩子。我们还将同一钩子用于允许代码(以编程方式tx)与当前事务关联和检索任意对象。并注册在事务提交之前/之后运行的作业。 总体代码很复杂,但是绝对可行。如果将JPATemplates用于程序化TX,则很难实现这一点。因此,我们完成了自己的模板,在完成拦截器工作之后便简单地调用了JPA模板。我们计划尽快开源我们的JPA库(在Spring的类之上编写)。 您可以在以下用于Postgresql的库中看到添加定制事务和带有Spring托管事务的挂钩的模式     

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...