如何在静态上下文/方法中获取spring bean(Hibernate Envers修订监听器)

时间:2018-06-06 09:31:59

标签: spring hibernate spring-data-jpa hibernate-envers

注意:这不是一个重复的问题。阅读整篇文章;)
为了防止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我有实用程序/ 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 Test框架进行集成测试时。根据Spring创建测试上下文和缓存bean的方式,bean查找类可能从另一个上下文而不是当前上下文返回实体管理器。如果你在其他测试中运行它而传递测试失败,则会发生这种情况(更多信息here)。

现在问题:
1.有没有办法将MyRevisionListener定义为Spring bean,以便我可以干净地注入持久化上下文?
2.如果以上是NO,那么如何在静态上下文/方法中正确获取持久化上下文?

感谢。

3 个答案:

答案 0 :(得分:2)

如果你有两个spring上下文,你可能想要获取调用监听器的那个。鉴于侦听器是无状态单例,建立侦听器调用与弹簧上下文之间关系的唯一方法是通过调用线程上下文来完成。

这是堆栈的某个地方,您需要将实体管理器保存到本地线程,然后从侦听器中获取它。

这样做的好地方是春季交易或实体经理创建的开始。

答案 1 :(得分:1)

我猜你可以将EntityManager注入测试并使用它在EntityManagerBeanLookup中设置它。

但我认为更清晰的解决方案是将上下文标记为脏,以便Spring创建一个新的。见How to force a fresh version of the Spring context BEFORE the test executes

答案 2 :(得分:0)

我最终使用了一个测试监听器(因为我们已经在测试监听器中使用了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;
    }

}