Note: it's not a duplicated question. Read the whole post ;)
To prevent XY problem, here's the full story:
I need to write an Envers revision listener to calculate and persist the diff of the most recent change of the entity in a spring Application:
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;
}
}
and to get EntityManager
bean I have the 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"));
}
}
Everything works just fine. The problem is when we have integration tests using Spring Test framework. Depending on how Spring creates test contexts and caches beans, it's possible that the bean lookup class returns an entity manager from another context, not the current context. It happens in our case when a passing test fails if you run it among other tests (more info here).
Now questions:
1. Is there any way to define MyRevisionListener
as a Spring bean, so that I can cleanly inject the persistence context?
2. If the above is a NO, then how can I properly get the persistence context in a static context/method?
Thanks.
I ended up using a test listener (since we're already using spring-test with our test listeners):
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;
}
}