如何在静态 context/method 中获取 spring bean(Hibernate Envers 修订监听器)
How to obtain a spring bean in a static context/method (Hibernate Envers revision listener)
注意:这不是重复的问题。阅读全文 post ;)
为了防止 XY 问题,这里是完整的故事:
我需要编写一个 Envers 修订侦听器来计算并保留 spring 应用程序中实体最近更改的差异:
public class MyRevisionListener implements EntityTrackingRevisionListener {
@Override
public void newRevision(Object revision) {
...
}
@Override
public void entityChanged(Class entityClass,
String entityName,
Serializable entityId,
RevisionType revisionType,
Object revisionEntity) {
EntityManager em = EntityManagerBeanLookup.getInstance().get();
int id = ((DefaultRevisionEntity) revisionEntity).getId();
List<?> revisions = AuditReaderFactory.get(em)
.createQuery()
.forRevisionsOfEntity(entityClass, false, true)
.add(AuditEntity.id().eq(entityId))
.add(AuditEntity.revisionNumber().le(id + 1))
.addOrder(AuditEntity.revisionNumber().desc())
.setMaxResults(2)
.getResultList();
checkArgument(revisions.size() > 0, "Need at least one revision: %s", revisions);
// continue with calculating and persisting the diff;
}
}
为了得到 EntityManager
bean,我有 utility/bean EntityManagerBeanLookup
:
@Component
public class EntityManagerBeanLookup implements Supplier<EntityManager> {
@Getter
private static EntityManagerBeanLookup instance;
@Getter
private EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Autowired
public void setInstance(EntityManagerBeanLookup instance) {
EntityManagerBeanLookup.instance = instance;
}
@Override
public EntityManager get() {
return ofNullable(getInstance())
.map(EntityManagerBeanLookup::getEntityManager)
.orElseThrow(() -> new IllegalStateException("Not initialized yet"));
}
}
一切正常。问题是当我们使用 Spring 测试框架进行集成测试时。根据 Spring 创建测试上下文和缓存 bean 的方式,bean 查找 class returns 实体管理器可能来自另一个上下文,而不是当前上下文。在我们的案例中,如果您在其他测试中 运行 通过测试失败(更多信息 )。
现在提问:
1. 有什么方法可以将 MyRevisionListener
定义为 Spring bean,以便我可以干净地注入持久性上下文?
2. 如果以上为否,那么如何才能在静态 context/method 中正确获取持久性上下文?
谢谢。
如果您有两个 spring 上下文,您可能希望获取调用侦听器的上下文。鉴于侦听器是无状态单例,在侦听器调用和它在其中完成的 spring 上下文之间建立关系的唯一方法是通过调用线程上下文。
在堆栈的某个位置,您需要将实体管理器保存到本地线程,然后在侦听器中从那里获取它。
这样做的好地方是 spring 事务或实体管理器创建的开始。
我想您可以将 EntityManager
注入测试并使用它在 EntityManagerBeanLookup
中设置它。
但我认为更简洁的解决方案是将上下文标记为脏,因此 Spring 创建一个新的。参见 How to force a fresh version of the Spring context BEFORE the test executes
我最终使用了一个测试监听器(因为我们已经在使用 spring-test 测试监听器):
public class StaticBeanLookupResetTestListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) {
testContext.getApplicationContext()
.getBeansOfType(StaticBeanLookup.class) // The marker interface
.forEach(this::resetSelfReference);
}
@SneakyThrows
private void resetSelfReference(String name, StaticBeanLookup bean) {
Method getter = bean.getClass().getDeclaredMethod("setInstance", type);
getter.setAccessible(true);
getter.invoke(bean, bean);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
注意:这不是重复的问题。阅读全文 post ;)
为了防止 XY 问题,这里是完整的故事:
我需要编写一个 Envers 修订侦听器来计算并保留 spring 应用程序中实体最近更改的差异:
public class MyRevisionListener implements EntityTrackingRevisionListener {
@Override
public void newRevision(Object revision) {
...
}
@Override
public void entityChanged(Class entityClass,
String entityName,
Serializable entityId,
RevisionType revisionType,
Object revisionEntity) {
EntityManager em = EntityManagerBeanLookup.getInstance().get();
int id = ((DefaultRevisionEntity) revisionEntity).getId();
List<?> revisions = AuditReaderFactory.get(em)
.createQuery()
.forRevisionsOfEntity(entityClass, false, true)
.add(AuditEntity.id().eq(entityId))
.add(AuditEntity.revisionNumber().le(id + 1))
.addOrder(AuditEntity.revisionNumber().desc())
.setMaxResults(2)
.getResultList();
checkArgument(revisions.size() > 0, "Need at least one revision: %s", revisions);
// continue with calculating and persisting the diff;
}
}
为了得到 EntityManager
bean,我有 utility/bean EntityManagerBeanLookup
:
@Component
public class EntityManagerBeanLookup implements Supplier<EntityManager> {
@Getter
private static EntityManagerBeanLookup instance;
@Getter
private EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Autowired
public void setInstance(EntityManagerBeanLookup instance) {
EntityManagerBeanLookup.instance = instance;
}
@Override
public EntityManager get() {
return ofNullable(getInstance())
.map(EntityManagerBeanLookup::getEntityManager)
.orElseThrow(() -> new IllegalStateException("Not initialized yet"));
}
}
一切正常。问题是当我们使用 Spring 测试框架进行集成测试时。根据 Spring 创建测试上下文和缓存 bean 的方式,bean 查找 class returns 实体管理器可能来自另一个上下文,而不是当前上下文。在我们的案例中,如果您在其他测试中 运行 通过测试失败(更多信息
现在提问:
1. 有什么方法可以将 MyRevisionListener
定义为 Spring bean,以便我可以干净地注入持久性上下文?
2. 如果以上为否,那么如何才能在静态 context/method 中正确获取持久性上下文?
谢谢。
如果您有两个 spring 上下文,您可能希望获取调用侦听器的上下文。鉴于侦听器是无状态单例,在侦听器调用和它在其中完成的 spring 上下文之间建立关系的唯一方法是通过调用线程上下文。
在堆栈的某个位置,您需要将实体管理器保存到本地线程,然后在侦听器中从那里获取它。
这样做的好地方是 spring 事务或实体管理器创建的开始。
我想您可以将 EntityManager
注入测试并使用它在 EntityManagerBeanLookup
中设置它。
但我认为更简洁的解决方案是将上下文标记为脏,因此 Spring 创建一个新的。参见 How to force a fresh version of the Spring context BEFORE the test executes
我最终使用了一个测试监听器(因为我们已经在使用 spring-test 测试监听器):
public class StaticBeanLookupResetTestListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) {
testContext.getApplicationContext()
.getBeansOfType(StaticBeanLookup.class) // The marker interface
.forEach(this::resetSelfReference);
}
@SneakyThrows
private void resetSelfReference(String name, StaticBeanLookup bean) {
Method getter = bean.getClass().getDeclaredMethod("setInstance", type);
getter.setAccessible(true);
getter.invoke(bean, bean);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}