Search code examples
javapostgresqlhibernatejpatransactional

Adding @Transactional is breaking UnitTests by stopping JPA from fetching the foreign keys


I am attempting to improve the speed of our flyway database tests. The original method was to run flyway.clear() and flyway.migrate() between each test run. The problem though is now we have some potentially long running flyway migration scripts and we don't want to have to re-run those between each test.

I have been trying to use the spring @Transactional annotation to roll back the database after each test, then we only need to run flyway once.

The problem is, as soon as I add transactions around the test JPA stops fetching the foreign keys. I am assuming this is because the nested transactions aren't behaving as I expect, but I haven't been able to figure out what I have configured wrong.

I have posted an example project here: ExampleSpringJpaHibernatePostgresqltest with the problem (See ExampleTest.java) but in summary:

I have the entities with a foreign key as follows (i have removed unnessisary to make this easier to read):

@Entity
@Table(name = "parent")
public class ParentEntity {
    @Id
    @Column
    private Integer id;

    @Column
    private String name;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "fk_parent", referencedColumnName = "id")
    private Set<ChildEntity> children;
}
@Entity
@Table(name = "child")
public class ChildEntity {
    @Id
    @Column
    private Integer id;

    @Column
    private String name;

    @Column(name = "fk_parent")
    private Integer fkParent;
}

I then have a test that uses parentRepository.findAll(). If I surround that test using @Transactional then the test fails because parentEntity.getChildren() is null. Without the transaction though, and running flyway clear/migrate between each test everything works as expected.

    @Test
    @Transactional
    public void testReadWithDependencies() {
        ParentEntity savedParent1 = parentRepo.save(new ParentEntity("parent1"));
        childRepo.save(new ChildEntity("child1", savedParent1.getId()));

        Set<ChildEntity> acutalChildren = parentRepo.findAll().get(0).getChildren();

        // XXX When the tests are run with @Transactional, this assertion fails.
        assertThat(acutalChildren).isNotNull();
    }

Solution

  • parentRepo.findAll does not refresh the data from the database and you don't add the child to the children in parent.

    Therefore there are no children assigned to the parent entity.

    So if you refresh the entity (I injected the EntityManger) your test is successful:

    @Test
    @Transactional
    public void testReadWithDependencies() {
        assertThat(parentRepo.findAll()).isEmpty();
        assertThat(childRepo.findAll()).isEmpty();
    
        ParentEntity savedParent1 = parentRepo.save(new ParentEntity("parent1"));
        childRepo.save(new ChildEntity("child1", savedParent1.getId()));
    
        List<ParentEntity> allParents = parentRepo.findAll();
        assertThat(allParents).hasSize(1);
    
        // Refresh
        ParentEntity parentEntity = allParents.get(0);
        entityManager.refresh(parentEntity);
    
        Set<ChildEntity> acutalChildren = parentEntity.getChildren();
    
        // XXX When the tests are run with @Transactional, this assertion fails.
        assertThat(acutalChildren).isNotNull();
    
        assertThat(acutalChildren).hasSize(1);
        assertThat(acutalChildren.iterator().next().getName()).isEqualTo("child1");
    }