Как вы тестируете Spring @Transactional, не нажимая кэш уровня 1 hibernate или не выполняя ручной сеанс flush?

использование Spring + Hibernate и транзакционных аннотаций.

я пытаюсь проверить следующие:

  1. вызовите метод, который изменяет объект пользователя, а затем вызывает @Transactional метод обслуживания, чтобы сохранить его
  2. прочитайте объект обратно из БД и убедитесь, что его значения верны после метода

первой проблемой, с которой я столкнулся, было чтение объекта User на Шаге 2, только что вернувшего его в кэш уровня Hibernate 1 и на самом деле не читается из базы данных.

поэтому я вручную выселил объект из кэша, используя сеанс для принудительного чтения из базы данных. Однако, когда я это делаю, значения объекта никогда не сохраняются в модульном тесте (я знаю, что он откатывается после завершения теста из-за настроек, которые я указал).

я попытался вручную промыть сеанс после вызова @Transactional метод обслуживания, и совершил изменения. Однако этого не произошло. чего я и ожидал. Я думал, что A @Transactional метод обслуживания гарантирует, что транзакция была зафиксирована и сеанс сброшен, прежде чем он вернется. Я знаю, что вообще весна решит, когда делать это управление, но я думал, что "единица работы" в @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);
}

метод DAO здесь:

@Override
public void updateUser(User user) {
    Session session = sessionFactory.getCurrentSession();
    session.update(user);
}

sesssionfactory является autowired:

@Autowired
@Qualifier("userSessionFactory")
private SessionFactory sessionFactory;

я использую конфигурацию контекста приложения XML. У меня:

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

и

<bean id="userDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> 
    <property name="driverClass" value="${user.jdbc.driverClass}"/>
    <property name="jdbcUrl" value="${user.jdbc.jdbcUrl}" />
    <property name="user" value="${user.jdbc.user}" />
    <property name="password" value="${user.jdbc.password}" />
    <property name="initialPoolSize" value="3" />
    <property name="minPoolSize" value="1" />
    <property name="maxPoolSize" value="17" />
</bean>

<bean id="userSessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="userDataSource" />
    <property name="configLocation" value="classpath:user.hibernate.cfg.xml" />
</bean>

<bean id="userTransactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="dataSource" ref="userDataSource" />
    <property name="sessionFactory" ref="userSessionFactory" />
</bean>

там также сканирование компонентов в классах services и dao. Как я уже сказал, это работает в производстве.

Я думал, что если у меня есть метод, помеченный @Transactional что к концу этого метода (например, метод обновления здесь) Spring заставит сеанс зафиксировать и сбросить.

я вижу только несколько вариантов:

  1. я что-то неправильно, хотя это работает для меня в целом (не только юнит-тесты). Какие-нибудь догадки? Любая идея как это проверить?

  2. что-то о конфигурации модульного теста сами по себе не ведут себя так, как приложение будет.

  3. транзакции и сеансы так не работают. Мой единственный вывод заключается в том, что Spring оставляет транзакцию и/или сеанс открытыми после вызова этого метода обновления. Поэтому, когда я вручную выселяю пользователя из объекта сеанса, эти изменения еще не были зафиксированы.

может кто-нибудь подтвердите, если это ожидаемое поведение? Не должен!--13--> заставили совершить и флеш на сессии? Если нет, то как можно проверить метод с пометкой @Transactional и что методы действительно работают с транзакциями?

т. е., как переписать модульный тест?

есть другие идеи?

2 ответов


вот с чем я столкнулся. Рассмотрим этот код в тестовом методе:

    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 действительно участвует. Если метод тестирования транзакционный, то каждое чтение происходит в одной транзакции, и я считаю, что они только попали в спящий режим кэш уровня 1 (и фактически не считывайте из базы данных). Однако, если метод теста не является транзакционным, то каждое чтение происходит в его собственной транзакции, и каждый попадает в базу данных. Таким образом, кэш уровня 1 hibernate привязан к управлению сеансом / транзакциями.

возьмите aways:

  1. даже если метод теста вызывает несколько транзакционных методов в другом классе, если этот метод теста сам является транзакционным, все эти вызовы происходят в одном торговая операция. Метод испытания является "единицей труда". Однако если тестовый метод не является транзакционным, то каждый вызов транзакционного метода в этом тесте выполняется в его собственной транзакции.

  2. мой тестовый класс был помечен @Transactional, поэтому каждый метод будет транзакционным, если не помечен переопределяющей аннотацией, такой как @AfterTransaction. Я мог бы так же легко не отмечать класс @Transactional и отмечать каждый метод @Транзакций

  3. кэш уровня 1 спящего режима кажется привязанным к транзакции при использовании Spring @Transactional. Т. е. последующие чтения объекта в той же транзакции попадут в кэш уровня 1 спящего режима, а не в базу данных. Обратите внимание, есть 2 уровня кэша и другие механизмы, которые вы можете настроить.

Я собирался использовать метод @ Transactional test, а затем использовать @AfterTransaction для другого метода в тестовом классе и отправить raw SQL оценка значений в базе данных. Это обойдет кэш ORM и hibernate уровня 1 в целом, гарантируя, что вы сравниваете фактические значения в БД.

простым ответом было просто убрать @Transactional из моего тестового класса. Ура.


Q

может ли кто-нибудь подтвердить, что это ожидаемое поведение? Не стоит @ Transaction заставили совершить и флеш на сессии? Если нет, то как будет ли один тестировать метод с пометкой @Transactional и что методы реально работать с транзакциями?

A

это ожидаемое поведение. Весной осознает транзакций модульного тестирования поддержки проектирования rollsback сделки после тест закончен. Это по замыслу.

неявная граница транзакции создается для каждого теста (каждый метод с @Test), и после завершения теста выполняется откат.

следствием этого является то, что после завершения всех тестов нет данных, которые фактически изменены. То есть цель состоит в том, чтобы быть больше "единицей" и меньше "интеграцией". Вы должны прочитать весна документации почему это выгодно.

если вы действительно хотите чтобы проверить сохраняемые данные и просмотреть эти данные за пределами границы транзакции, я рекомендую более сквозной тест, такой как функциональные/интеграционные тест, такой как selenium или нажатие внешнего WS/REST API, если у вас есть.

Q

И. Е., как я должен переписать здесь мой модульного тестирования?

A

модульный тест не работает, потому что вы session.evict и не промывать. Выселение приведет к тому, что изменения не будут синхронизированы, даже если вы уже вызвали session.update ваш в транзакции и спящий режим пакетных операций. Это более ясно с raw SQL, поскольку hibernate откладывает сохранение для пакетной обработки всех операций, поэтому он ждет, пока сеанс не будет сброшен или закрыт для связи с базой данных для производительности. Если ты ... --2--> хотя SQL будет выполнен сразу (т. е. обновление действительно произойдет), а затем вы можете выселить, если хотите заставить перечитывать но ваше перечитывание в рамках сделки. Я на самом деле довольно уверен flush вызовет выселение, поэтому нет необходимости звонить evict чтобы заставить перечитывать, но я могу ошибаться.