繁体   English   中英

在 hibernate 中测试延迟加载(带有事务的 java spring)

[英]Testing lazy loading in hibernate (java spring with transactions)

我有以下设置:两个实体(父母,孩子),孩子应该延迟加载到父母中,通过使用自定义存储库ParentCustomRepository JpaRepository调用Hibernate.initialize 以下代码按预期工作。

@Entity(name = "children")
public class Child {

    @GeneratedValue
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "parent_id", nullable = false)
    private Parent parent;

    @Column
    private String name;

    // Getter and Setter...
}
@Entity(name = "parents")
public class Parent {

    @GeneratedValue
    @Id
    private Long id;

    @OneToMany(
            mappedBy = "parent",
            fetch = FetchType.LAZY
    )
    private List<Child> children;

    @Column
    private String name;

    // Getter and Setter...
}
public interface ChildRepository extends JpaRepository<Child, Long> {}
public interface ParentRepository extends JpaRepository<Parent, Long>, ParentCustomRepository {}
public interface ParentCustomRepository {
    List<Parent> findAllWithChildren();
}
@Transactional
@Component
public class ParentCustomRepositoryImpl implements ParentCustomRepository {

    private final ParentRepository parentRepository;

    @Lazy
    @Autowired
    public ParentCustomRepositoryImpl(ParentRepository parentRepository) {
        this.parentRepository = parentRepository;
    }

    @Override
    public List<Parent> findAllWithChildren() {
        final List<Parent> parents = this.parentRepository.findAll();
        parents.forEach(parent -> {
            Hibernate.initialize(parent.getChildren());
        });
        return parents;
    }
}

到目前为止,一切都很好。 但是,我无法让测试以可接受的方式工作。 我希望拥有彼此独立的测试,这样每个测试都不需要新的上下文。 这是一个简单的测试 class 可以说明我的意思。

@SpringBootTest
// @Transactional // uncomment this line to enable auto rollback after each test.
@ActiveProfiles("test")
public class ExampleTest {

    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Autowired
    private ParentRepository parentRepository;

    @Autowired
    private ChildRepository childRepository;

    @BeforeEach
    public void setUp() {
        if (parentRepository.count() > 0) {
            LOG.info("Already Set Up");
            return;
        }

        LOG.info("Set Up");

        Parent christian = new Parent();
        christian.setName("Christian");

        christian = parentRepository.save(christian);

        Child alex = new Child();
        alex.setName("Alex");
        alex.setParent(christian);

        alex = childRepository.save(alex);

        Child daniela = new Child();
        daniela.setName("Daniela");
        daniela.setParent(christian);

        daniela = childRepository.save(daniela);

        christian.setChildren(Arrays.asList(alex, daniela));

        // commit transaction when using @Transactional 
    }


    @Test
    public void givenStoredParent_whenGetAllChildren_thenCorrectSize() {
        checkSize();
    }

    @Test
    public void givenStoredParent_whenGetAllChildren_thenCorrectSize_secondTime() {
        checkSize();
    }

    @Test
    public void givenStoredParent_whenAddChild_thenUpdatedSize() {
        addChild();
    }

    @Test
    public void givenStoredParent_whenAddChild_thenUpdatedSize_secondTime() {
        addChild();
    }

    public void addChild() {
        assertThat(childRepository.count()).isEqualTo(2);

        final List<Parent> parents = parentRepository.findAllWithChildren();
        assertThat(parents.size()).isEqualTo(1);

        Parent christian = parents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(2);

        Child peter = new Child();
        peter.setName("Peter");
        peter.setParent(christian);

        childRepository.saveAndFlush(peter);

        assertThat(childRepository.count()).isEqualTo(3);

        assertThat(childRepository.findAll()).allMatch(child -> {
            return child.getParent().getName().equals("Christian");
        });

        final List<Parent> updatedParents = parentRepository.findAllWithChildren();
        assertThat(updatedParents.size()).isEqualTo(1);

        christian = updatedParents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(3);
    }

    public void checkSize() {
        final List<Parent> parents = parentRepository.findAllWithChildren();
        assertThat(parents.size()).isEqualTo(1);

        Parent christian = parents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(2);
    }
}
  • 如果我在没有@Transactional注释的情况下运行这些测试,则每个save操作都会将更改保留在数据库中,这会导致其余测试中的不同结果。
    • 我知道我可以在每次测试后使用带有@AfterEach注释的 tearDown 方法清除数据库。 这将为每个测试带来一个干净的开始,并且对于这个例子来说似乎很好。 但是,我正在为我的公司开发的应用程序依赖于一个更大的初始化数据库,这需要相当长的时间来进行数百次测试。 (这也是我不想在测试中的任何地方使用@DirtiesContext的原因)
  • 如果我使用@Transactional注释运行这些测试,则每次测试后都会执行回滚。 这实际上是我正在寻找的行为。 正如您在测试 output 的结果中看到的,一些测试失败了。 这是因为childRepository.save(peter)不会持久化添加的孩子,因此调用 `parentRepository.findAllWithChildren() 只会返回从一开始就在数据库中的孩子。
    • 我知道我可以通过提交来保持事务,但是我将不再有独立的测试。
    • 在测试 class 中使用@Transactional(isolation = Isolation.READ_UNCOMMITTED)不起作用。 由于我不想在实际的程序代码中使用这种隔离级别,所以我没有在这个方向上做进一步的研究。

我的假设是我需要commit才能访问父级与其子级之间的更新关系。 提交也会清除所有保存点,因此即使使用自定义事务管理,我也无法执行回滚。

由于我花了几天时间寻找满足我需求的解决方案但没有成功,我请教你们中的一些人是否已经处理过此类问题并且可以提供解决方案和/或解释。 是否有一些我不知道的最佳实践?

亚历克斯

您可以只创建一个 @AfterEach 方法,在其中恢复初始数据库(例如,如果存在,则删除添加的行等)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM