Как вы тестируете Spring @Transactional, не нажимая кэш уровня 1 hibernate или не выполняя ручной сеанс flush?
использование Spring + Hibernate и транзакционных аннотаций.
я пытаюсь проверить следующие:
- вызовите метод, который изменяет объект пользователя, а затем вызывает
@Transactional
метод обслуживания, чтобы сохранить его - прочитайте объект обратно из БД и убедитесь, что его значения верны после метода
первой проблемой, с которой я столкнулся, было чтение объекта 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 заставит сеанс зафиксировать и сбросить.
я вижу только несколько вариантов:
я что-то неправильно, хотя это работает для меня в целом (не только юнит-тесты). Какие-нибудь догадки? Любая идея как это проверить?
что-то о конфигурации модульного теста сами по себе не ведут себя так, как приложение будет.
транзакции и сеансы так не работают. Мой единственный вывод заключается в том, что 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:
даже если метод теста вызывает несколько транзакционных методов в другом классе, если этот метод теста сам является транзакционным, все эти вызовы происходят в одном торговая операция. Метод испытания является "единицей труда". Однако если тестовый метод не является транзакционным, то каждый вызов транзакционного метода в этом тесте выполняется в его собственной транзакции.
мой тестовый класс был помечен @Transactional, поэтому каждый метод будет транзакционным, если не помечен переопределяющей аннотацией, такой как @AfterTransaction. Я мог бы так же легко не отмечать класс @Transactional и отмечать каждый метод @Транзакций
кэш уровня 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
чтобы заставить перечитывать, но я могу ошибаться.