使用Hibernate Envers进行集成测试

12

我正在尝试构建一些关于经过审计的实体的测试。我的问题是envers仅在事务提交时进行审计。

我需要创建/编辑一些测试对象,提交事务,然后检查修订记录。

使用envers进行集成测试的最佳方法是什么?

更新:这是一个非常糟糕、不确定的测试类,展示了我想要实现的内容。我希望在不依赖测试方法顺序的情况下完成这个过程。

首先,在单个事务中创建一个帐户和一个帐户交易。这两个经过审计的条目都是修订版本1。

第二步,在新的事务中更新帐户交易。经过审计的条目在修订版本2。

第三步,加载修订版本1的经过审计的帐户,并对其进行操作。

@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class TestAuditing {

    @Autowired
    private AccountDao accountDao;

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    @Rollback(false)
    public void first() {
        Account account = account("Test Account", "xxxxxxxx", "xxxxxx");

        AccountTransaction transaction = transaction(new Date(), Deposit, 100, "Deposit");
        account.setTransactions(newArrayList(transaction));

        accountDao.create(account);
    }

    @Test
    @Rollback(false)
    public void second() {
        Account account = accountDao.getById(1L);
        AccountTransaction transaction = account.getTransactions().get(0);
        transaction.setDescription("Updated Transaction");
        accountDao.update(account);
    }

    @Test
    public void third() {
        AuditReader reader = AuditReaderFactory.get(entityManager);

        List<Number> accountRevisions = reader.getRevisions(Account.class, 1L);
        //One revision [1]

        List<Number> transactionRevisions = reader.getRevisions(AccountTransaction.class, 1L);
        //Two revisions [1, 2]

        Account currentAccount = accountDao.getById(1L);
        Account revisionAccount = (Account) reader.createQuery().forEntitiesAtRevision(Account.class, 1).getSingleResult();

        System.out.println(revisionAccount);
    }

1
点击这里查看——无耻的自我推销。 - Tomasz Nurkiewicz
谢谢您的回复,Tomasz。不过我仍然不确定如何从您的博客文章中解决我的问题。我并不真正担心懒加载等方面的误报,更关注实际提交一些事务以设置一些审计测试数据。也许我在您的文章中错过了一些显而易见的东西? - Karl Walsh
1
好的,把我的文章滚动到DbResetRule - 我的想法是避免在JUnit测试中使用@Transactional,而是让您的代码提交和回滚事务。显然,这使得测试不可重复且脆弱。但是,我建议不要回滚更改,而是在每个测试之前/之后倾倒数据库并恢复它。代码是用Scala编写的,但这只是一个通用想法。如果这符合您的要求,请告诉我,我会在另一个答案中详细说明。 - Tomasz Nurkiewicz
我正在使用内存数据库,因此不需要重置数据库。手动执行事务将是很棒的选择,但我不知道如何操作。当我尝试使用entityManager.getTransaction().begin()时,会出现以下错误:不允许在共享的EntityManager上创建事务-请改用Spring事务或EJB CMT。 - Karl Walsh
2
当您使用@Transactional注释方法时,您不必手动执行任何操作。看起来您已经在这样做了。但是,当您从测试中删除@Transactional时,您不能在测试中简单地使用EntityManager。 要么将其访问包装在事务型bean中,要么使用TransactionTemplate - Tomasz Nurkiewicz
1
太棒了,正是我所需要的。我已将我的代码添加为答案。 - Karl Walsh
5个回答

5
根据Tomasz的建议,我使用了TransactionTemplate来实现每个dao操作后的提交。没有类级别的@Transactional注释。
Envers审计条目在方法完成之前插入,这正是我需要的。
@ContextConfiguration("testApplicationContext.xml")
public class TestAuditing extends AbstractJUnit4SpringContextTests {

    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Autowired
    private PersonDao personDao;

    private TransactionTemplate template;

    @Before
    public void transactionTemplate() {
         template = new TransactionTemplate(platformTransactionManager);
    }

    @Test
    public void test() {
        Person person = createInTransaction(person("Karl", "Walsh", address("Middle of nowhere")), personDao);
        System.out.println(person);
    }

    private <T> T createInTransaction(final T object, final Dao<?, T> dao) {
        return template.execute(new TransactionCallback<T>() {
            public T doInTransaction(TransactionStatus transactionStatus) {
                dao.create(object);
                return object;
            }
        });
    }
}

5
我是Spring事务测试支持的用户,它在测试完成时回滚测试,由于envers的设计,修订版本不会被创建。我创建了一个hack,似乎可以让人们在事务提交之前手动地“告诉”envers去做它的工作,但允许Spring继续回滚。
以下代码段应该有所帮助。 1. 创建自己的审计监听器,覆盖现有的envers审计监听器。这允许访问静态成员,可见于单元测试。可能有更好的方法,但它起作用。
public class AuditEventListenerForUnitTesting extends AuditEventListener {

   public static AuditConfiguration auditConfig;

   @Override
   public void initialize(Configuration cfg) {
      super.initialize(cfg);
      auditConfig = super.getVerCfg();
   }
}

修改你的persistence.xml文件,将这个新的监听器类包含进去,代替envers提供的原来的监听器。

(如果需要,可以对其他监听器重复进行相同操作)

现在,在"单元测试"中:

{
   saveNewList(owner); //code that does usual entity creation
   em.flush();  
   EventSource hibSession = (EventSource) em.getDelegate();
   AuditEventListenerForUnitTesting.auditConfig.getSyncManager().get(hibSession).doBeforeTransactionCompletion(hibSession);     
   //look for envers revisions now, and they should be there
}

我需要这个是因为我有一些针对与版本控制表连接的Hibernate实体的JDBC查询。


2
这是受此前的答案的强烈启示,针对Envers 4.2.19.Final(JPA 2.0)进行了调整。在我的情况下,该解决方案不需要事务提交。
首先创建以下实现org.hibernate.integrator.spi.Integrator的内容,并将其添加到类路径中:
public class MyIntegrator implements Integrator {

  public static AuditConfiguration auditConfig;

  @Override
  public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory,
    SessionFactoryServiceRegistry serviceRegistry) {
    auditConfig = AuditConfiguration.getFor(configuration);
  }

  @Override
  public void integrate(MetadataImplementor metadata, SessionFactoryImplementor sessionFactory,
    SessionFactoryServiceRegistry serviceRegistry) {
    // NOP
  }

  @Override
  public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    // NOP
  }

}

然后在src/test/resources下创建一个META-INF/services/org.hibernate.integrator.spi.Integrator文件,并将集成器类的完整限定名称粘贴到其中。

在测试中,调用您的DAO方法,刷新Hibernate会话,在完成后告诉Envers继续:

EventSource es = (EventSource) entityManager.getDelegate();
SessionImplementor si = entityManager.unwrap(SessionImplementor.class);
MyIntegrator.auditConfig.getSyncManager().get(es).doBeforeTransactionCompletion(si);

你可以测试你的数据库内容,最后回滚事务。


1

针对Envers 5.4.15.Final@thomas-naskali's answer的更新版本。

    protected void flushEnvers()
    {
        final EventSource eventSource = entityManager.unwrap(EventSource.class);
        final Session session = entityManager.unwrap(Session.class);
        final SessionImplementor sessionImplementor = entityManager.unwrap(SessionImplementor.class);

        final EnversService service = session.getSessionFactory()
            .getSessionFactoryOptions()
            .getServiceRegistry()
            .getService(EnversService.class);

        service.getAuditProcessManager().get(eventSource)
            .doBeforeTransactionCompletion(sessionImplementor);
    }

这个!我需要添加session.flush()来获得我需要的行为,但是这个。 - Charles Hasegawa

-1

另外两种解决方案对我没有用,所以我采用了另一种方式,我只是创建了一个新的事务并强制提交。每次需要新版本时,我都会再次执行此操作。

@Autowired
@Qualifier("transactionManager")
private PlatformTransactionManager platformTransactionManager;

@Test
public void enversTest() throws Exception{
    Entity myEntity = new Entity();

    TransactionStatus status = platformTransactionManager.getTransaction(new DefaultTransactionDefinition());
    myEntity.setName("oldName");
    myEntity = entityDao.merge(myEntity);
    platformTransactionManager.commit(status); // creates a revision with oldname

    TransactionStatus newStatus = platformTransactionManager.getTransaction(new DefaultTransactionDefinition());
    myEntity.setName("newName");
    myEntity = entityDao.merge(myEntity);
    platformTransactionManager.commit(newStatus); // creates a new revision with newName
}

如果使用 @Transactional(transactionManager="transactionManager") ,它可能会绕过提交并将每个测试视为一个事务(因此在同一测试中不会多次进行版本控制...)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接