Как вы тестируете Spring @Transactional, не обращаясь к кэшу 1-го уровня гибернации или не делая ручную очистку сессии?
Использование Spring + Hibernate и транзакционных аннотаций.
Я пытаюсь проверить следующее:
вызовите метод, который изменяет объект пользователя, затем вызывает@Transactional
метод обслуживания, чтобы сохранить егопрочитайте объект обратно из БД и убедитесь, что его значения верны после методаПервая проблема, с которой я столкнулся при чтении объекта User на шаге 2, просто вернула объект в кэш-памяти уровня 1 Hibernate и фактически не читала из базы данных.
Поэтому я вручную извлек объект из кэша, используя Session для принудительного чтения из базы данных. Однако, когда я делаю это, значения объекта никогда не сохраняются в модульном тесте (я знаю, что он откатывается после завершения теста из-за настроек, которые я указал).
Я попытался вручную сбросить сессию после вызова@Transactional
метод обслуживания, и что DID фиксирует изменения. Однако я не ожидал этого. Я думал, что@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);
}
Метод DAO здесь:
@Override
public void updateUser(User user) {
Session session = sessionFactory.getCurrentSession();
session.update(user);
}
SesssionFactory имеет автоматическую связь:
@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>
Также есть проверка компонентов на сервисах и классах дао. Как я уже сказал, это работает в производстве.
Я думал, что если у меня есть помеченный метод@Transactional
что к концу этого метода (например, метод обновления здесь) Spring заставит Session зафиксировать и очистить.
Я вижу только несколько вариантов:
Я что-то неправильно настроил, хотя это работает для меня в целом (только не модульные тесты). Есть догадки? Есть идеи, как это проверить?
Что-то в самих настройках модульного теста ведет себя не так, как приложение.
Транзакции и сессии не работают так. Мой единственный вывод заключается в том, что Spring оставляет транзакцию и / или сессию открытой после вызова этого метода обновления. Поэтому, когда я вручную выселяю пользователя из объекта Session, эти изменения еще не зафиксированы.
Кто-нибудь может подтвердить, является ли это ожидаемым поведением? не должны@Transaction
вынудили совершить и сбросить сессию? Если нет, то как можно проверить метод, отмеченный@Transactional
и что методы на самом деле работают с транзакциями?
То есть, как мне переписать свой юнит-тест здесь?
Есть еще идеи?