问题描述
||
我正在使用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托管事务的挂钩的模式