如何测试Spring@Transactional,而不只是点击hibernate 1级缓存或手动刷新会话?

如何测试Spring@Transactional,而不只是点击hibernate 1级缓存或手动刷新会话?,spring,hibernate,transactional,sessionfactory,transactionmanager,Spring,Hibernate,Transactional,Sessionfactory,Transactionmanager,使用Spring+Hibernate和事务性注释 我正在尝试测试以下内容: 调用更改用户对象的方法,然后调用@Transactional服务方法来持久化它 从数据库中读回对象,并确保方法完成后其值正确无误 我遇到的第一个问题是在步骤2中读取用户对象,它只是返回了Hibernate 1级缓存中的一个对象,并没有实际从数据库中读取 因此,我使用会话从缓存中手动逐出该对象,以强制从数据库中读取。但是,当我这样做时,对象值永远不会在单元测试中持久化(我知道,由于我指定的设置,它会在测试完成后回滚) 在调

使用Spring+Hibernate和事务性注释

我正在尝试测试以下内容:

  • 调用更改用户对象的方法,然后调用
    @Transactional
    服务方法来持久化它
  • 从数据库中读回对象,并确保方法完成后其值正确无误
  • 我遇到的第一个问题是在步骤2中读取用户对象,它只是返回了Hibernate 1级缓存中的一个对象,并没有实际从数据库中读取

    因此,我使用会话从缓存中手动逐出该对象,以强制从数据库中读取。但是,当我这样做时,对象值永远不会在单元测试中持久化(我知道,由于我指定的设置,它会在测试完成后回滚)

    在调用
    @Transactional
    服务方法之后,我尝试手动刷新会话,但确实提交了更改。然而,这并不是我所期望的。我认为
    @Transactional
    服务方法可以确保在事务返回之前提交事务并刷新会话。 我知道Spring通常会决定何时进行这种管理,但我认为
    @Transactional
    方法中的“工作单元”就是这种方法

    在任何情况下,现在我都在试图弄清楚如何测试
    @Transactional
    方法

    下面是一个失败的junit测试方法:

    @RunWith(SpringJUnit4ClassRunner.class)
    @Transactional
    @TransactionConfiguration(transactionManager = "userTransactionManager", defaultRollback = true)
    @WebAppConfiguration()
    @ContextConfiguration(locations = { "classpath:test-applicationContext.xml",
            "classpath:test-spring-servlet.xml",
            "classpath:test-applicationContext-security.xml" })
    public class HibernateTest {
    
        @Autowired
        @Qualifier("userSessionFactory")
        private SessionFactory sessionFactory;
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private PaymentService paymentService;
    
        @Autowired
        private QueryService queryService;
    
        @Autowired
        private NodeService nodeService;
    
        @Autowired
        private UserUtils userUtils;
    
        @Autowired
        private UserContext userContext;
    
      @Test
        public void testTransactions() {
            // read the user
            User user1 = userService.readUser(new Long(77));
            // change the display name
            user1.setDisplayName("somethingNew");
            // update the user using service method that is marked @Transactional
            userService.updateUserSamePassword(user1);
            // when I manually flush the session everything works, suggesting the
            // @Transactional has not flushed it at the end of the method marked
            // @Transactional, which implies it is leaving the transaction open?
            // session.flush();
            // evict the user from hibernate level 1 cache to insure we are reading
            // raw from the database on next read
            sessionFactory.getCurrentSession().evict(user1);
            // try to read the user again
            User user2 = userService.readUser(new Long(77));
            System.out.println("user1 displayName is " + user1.getDisplayName());
            System.out.println("user2 displayName is " + user2.getDisplayName());
            assertEquals(user1.getDisplayName(), user2.getDisplayName());
        }
    }
    
    如果手动刷新会话,则测试成功。但是,我希望
    @Transactional
    方法负责提交和刷新会话

    updateUserSamePassword的服务方法如下:

    @Transactional("userTransactionManager")
    @Override
    public void updateUserSamePassword(User user) {
        userDAO.updateUser(user);
    }
    
    @Override
    public void updateUser(User user) {
        Session session = sessionFactory.getCurrentSession();
        session.update(user);
    }
    
    DAO方法如下:

    @Transactional("userTransactionManager")
    @Override
    public void updateUserSamePassword(User user) {
        userDAO.updateUser(user);
    }
    
    @Override
    public void updateUser(User user) {
        Session session = sessionFactory.getCurrentSession();
        session.update(user);
    }
    
    SessionFactory是自动连接的:

    @Autowired
    @Qualifier("userSessionFactory")
    private SessionFactory sessionFactory;
    
    我正在使用XML应用程序上下文配置。我有:

    <context:annotation-config />
    <tx:annotation-driven transaction-manager="userTransactionManager" />
    
    
    

    
    
    还对服务和dao类进行了组件扫描。正如我所说,这在生产中起作用

    我认为,如果我有一个标记为
    @Transactional
    的方法,在该方法结束时(例如,这里的update方法),Spring将强制会话提交并刷新

    我只能看到几个选项:

  • 我错误地配置了一些东西,尽管这在一般情况下对我有效(只是不是单元测试)。猜猜看?关于如何测试这个有什么想法吗

  • 单元测试配置本身的某些方面并不像应用程序那样运行

  • 事务和会话不是这样工作的。我唯一的推论是Spring在调用该更新方法后将事务和/或会话保持打开状态。因此,当我手动退出会话对象上的用户时,这些更改尚未提交

  • 有人能确认这是否是预期的行为吗?
    @Transaction
    不应该在会话上强制提交和刷新吗?如果不是,那么如何测试标记为
    @Transactional
    的方法,以及这些方法是否实际用于事务

    也就是说,我应该如何在这里重写我的单元测试

    还有其他想法吗?

    Q

    有人能确认这是否是预期的行为吗?不应该 @事务已在会话上强制提交和刷新?如果没有,怎么办 有人会测试标记为@Transactional的方法吗 实际处理事务

    A

    。spring感知的事务单元测试支持通过设计在测试完成后回滚事务。这是故意的

    每个测试(每个带有@test的方法)都会创建一个隐式事务边界,一旦测试完成,就会执行回滚

    这样做的结果是,在所有测试完成后,没有实际更改的数据。这就是我们的目标,更像“单元”,而不是像“集成”。你应该阅读这篇文章来解释为什么这是有利的

    如果您真的想测试持久化的数据并查看事务边界之外的数据,我建议您进行一种更为端到端的测试,如selenium测试,或者如果您有外部WS/REST API,则点击它

    Q

    也就是说,我应该如何在这里重写我的单元测试

    A


    您的单元测试不起作用,因为您没有刷新。逐出将导致更改不同步,即使您已在事务和休眠批处理操作中调用了
    session.update
    。对于原始SQL,这一点更为明显,因为hibernate延迟持久化以批处理所有操作,因此它会等待会话刷新或关闭,以便与数据库通信以提高性能。如果您
    session.flush
    ,尽管SQL将立即执行(即更新将真正发生),然后您可以退出,如果您想强制重新读取,但您可以在事务中重新读取。实际上,我很确定
    flush
    会导致逐出,因此没有必要调用
    execute
    来强制重新读取,但我可能错了。

    以下是我遇到的问题。在测试方法中考虑这个代码:

        String testDisplayNameChange = "ThisIsATest";
        User user = userService.readUser(new Long(77));
        user.setDisplayName(testDisplayNameChange);
        user = userService.readUser(new Long(77));
        assertNotEquals(user.getDisplayName(), testDisplayNameChange);
    
    请注意,方法userService.readUser在服务类中标记为@Transactional

    如果该测试方法标记为@Transactional,则测试失败。如果不是,它就会成功。现在我不确定Hibernate缓存是否/何时真正参与其中。如果测试方法是事务性的,那么每次读取都发生在一个事务中,我相信它们只会命中Hibernate 1级缓存(实际上不会从数据库中读取)。但是,如果测试方法不是事务性的,那么每次读取都发生在它自己的事务中,并且每次读取都会命中数据库。因此,hibernate 1级缓存与会话/事务管理绑定

    外卖:

  • 即使测试方法正在调用多个事务