Использование двунаправленных ассоциаций из объектов домена в @Transactional Junit Tests

У меня настроен тест JUnit @Transactional, и я хочу сохранить некоторые тестовые данные в базе данных, а также проверить правильность ассоциаций. Однако при тестировании ассоциаций они всегда оцениваются как нулевые, даже если это работает в нетранзакционном тесте.

Я сохраняю два объекта, используя аннотацию @Before:

@Before
public void testData() {
    TestObjectOne o = new TestObjectOne();
    o.setName("One");
    repo.saveEntity(o); //autowired

    TestObjectTwo t = new TestObjectTwo();
    t.setOne(o);
    t.setName("Two");
    repo.saveEntity(t);
}

При доступе к этим двум объектам в тесте я получаю правильные экземпляры:

    TestObjectOne o = repo.getOneByName("One");
    TestObjectOne t = repo.getTwoByName("Two");

При проверке связи между t и o я получаю правильную ссылку, потому что я явно определил эту связь:

    Assert.assertNotNull(t.getOne());

Но при проверке наоборот объект o обновляется некорректно:

    Assert.assertNotNull(o.getTwos());

Ассоциация определяется в объекте предметной области как

In One:

   @OneToMany(mappedBy = "one", fetch = FetchType.EAGER)
   private List<Two> twos;

In Two:

  @ManyToOne(optional = false)
  private One one;

Если я запускаю тест не как @Transactional, он работает хорошо.

edit сохранение объектов внутри теста вместо метода @Before не имеет никакого значения.


person chzbrgla    schedule 12.07.2011    source источник


Ответы (2)


Вам необходимо сбросить и очистить текущий сеанс после сохранения и перед загрузкой тестовых объектов для утверждений.

Когда вы сохраняете TestObjectOne o, его экземпляр сохраняется в сеансе Hibernate. Затем вы создаете TestObjectTwo t и добавляете ссылку на o и сохраняете ее, чтобы этот экземпляр теперь также сохранялся в сеансе Hibernate. Затем, когда вы вызываете get Hibernate, вы получаете экземпляры, которые вы создали ранее, без обновления их для отражения фактического состояния. Поэтому вам нужно сбросить и очистить сеанс перед загрузкой - тогда кеш сеанса L1 будет пуст, и сущности будут загружены правильно.

public class MyTest {

    @Before
    public void setUp() {

        TestObjectOne o = new TestObjectOne();
        o.setName("One");
        repo.saveEntity(o); //autowired

        TestObjectTwo t = new TestObjectTwo();
        t.setOne(o);
        t.setName("Two");
        repo.saveEntity(t);

        /* push all changes to current transaction*/
        repo.getSessionFactory().getCurrentSession().flush();
        /* clean current session, so when loading objects they'll
           be created from sratch and will contain above changes */
        repo.getSessionFactory().getCurrentSession().clear();
    }

    @Test
    public void test() {
        TestObjectOne o = repo.getOneByName("One");
        TestObjectOne t = repo.getTwoByName("Two");

        Assert.assertNotNull(t.getOne());
        Assert.assertNotNull(o.getTwos());
    }
}
person Roadrunner    schedule 15.07.2011
comment
очистка сеанса действительно сделала свою работу. Благодарю. тем не менее, я чувствую, что немного раздражает заботиться о сеансе во время теста, пока он правильно управляется через spring в производственной среде :(. есть ли другой способ проверить это? тест может дать ложный положительный результат или ложноотрицательный, потому что я забыл сбросить/очистить - что мне не нужно делать в производственном коде :( - person chzbrgla; 15.07.2011
comment
Если Вы хотите единую транзакцию с откатом в конце - нет, думаю, другого выхода нет. Именно так работает Hibernate — сущность заполняется данными из БД только один раз, когда она загружается в текущий сеанс. Когда вы очищаете сеанс, Hibernate выполнит обновление ваших данных в БД, но не изменит сущности в текущем сеансе. На самом деле это будет работать так же в производственном коде, если вы вызовете его в одной транзакции. Это будет работать правильно, только если вы вызовете load в другой транзакции после save. - person Roadrunner; 15.07.2011
comment
Чтобы немного упростить задачу, я обычно пишу базовый класс для транзакционных тестов, который @Autowires SessionFactory и имеет единственный метод flushAndClear(), который просто берет текущий сеанс и вызывает flush() и clear(), но вам все равно нужно не забыть вызвать его и перезагрузить объекты перед утверждения. - person Roadrunner; 15.07.2011

Аннотация @Transactional запускает откат после теста, поэтому результаты не сохраняются в базе данных.

Для тестов, требующих сохранения данных, вы можете добавить аннотацию @Rollback(false) в дополнение к аннотации @Transactional.

person Pau Giner    schedule 13.07.2011
comment
Странно то, что в вашем случае некоторые элементы сохраняются (объекты), а другие элементы - нет (отношения). Возможно, это связано с используемой базовой технологией персистентности (JPA+Hibernate?). - person Pau Giner; 13.07.2011
comment
Я знаю, что @Transactional вызывает откат - это именно то, что я хочу: p. Однако я хочу, чтобы ассоциации между объектами распространялись даже внутри транзакции. И это то, чего сейчас не происходит. Я тут как бы застрял :p - person chzbrgla; 14.07.2011