我正在使用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);
}
}
答案 0 :(得分:3)
@Filip
据我了解,您的要求是:
很自然地,您正在考虑Spring提供的TransactionSynchronizationManager作为存储唯一令牌的工具(在本例中为UID)
要非常小心这种方法,TransactionSynchronizationManager是管理Spring的所有@Transactional处理的主要存储助手。在@Transactional引擎下,Spring正在创建一个合适的EntityManager,一个适当的Synchronization对象,并使用TransactionSynchronizationManager将它们附加到本地线程。
在您的服务类代码中,在@Transactional方法中,您正在篡改同步对象,它可能会导致不良行为。
我已经对@Transactional如何在这里工作进行了深入分析,看看:http://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/
现在回到你的需求。你能做的是:
创建一个新的注释,假设@Auditable(uid =“AuditScenario1”)注释需要审核的方法,并使用Spring AOP拦截这些方法调用并为您管理Thread本地处理
示例:
修改过的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的类之上编写)。
中看到添加自定义事务和挂钩与Spring托管交易的模式