【问题标题】:Testing lazy loading in hibernate (java spring with transactions)在休眠中测试延迟加载(带有事务的java spring)
【发布时间】:2023-03-20 02:19:01
【问题描述】:

我有以下设置: 两个实体(父、子),子实体应该延迟加载到父实体中,通过使用自定义存储库 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;
    }
}

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

@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 注释运行这些测试,每次测试后都会执行回滚。这实际上是我正在寻找的行为。正如您在测试输出的结果中看到的那样,一些测试失败了。这是因为childRepository.save(peter) 不会持久化添加的孩子,因此对`parentRepository.findAllWithChildren() 的调用只会返回从一开始就在数据库中的孩子。
    • 我知道我可以通过提交来持久化事务,但是我将不再进行独立测试。
    • 在测试类中使用@Transactional(isolation = Isolation.READ_UNCOMMITTED) 不起作用。由于我不想在实际的程序代码中使用这种隔离级别,所以我没有在这个方向上做进一步的研究。

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

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

亚历克斯

【问题讨论】:

  • 为什么要测试spring存储库是否有效?那是框架的核心特性,你不需要测试它是否将东西持久化到数据库中......我只需要编写一个测试来检查 spring 上下文是否已初始化并且 bean 是否可用并且可以从预填充的数据库中读取一些东西。如果“保存”有效,测试有什么好处?
  • @Nadir 这些代码 sn-ps 只是一个例子,当然我不只是想测试保存是否有效。实际测试要复杂得多。
  • 那为什么不模拟存储库 bean?
  • @Nadir 当我模拟存储库 bean 时,只有当我手动将它们添加到列表中时,父级及其子级之间的关系才存在。这个例子听起来不错,但在更大的应用程序中,这会导致很多复杂的模拟 bean。其实,我正在寻找一个简单的解决方案,但恐怕没有这样的解决方案。

标签: java spring hibernate junit spring-transactions


【解决方案1】:

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

【讨论】:

  • 这需要更复杂方法的逻辑知识。如果开发人员 A 编写测试并且开发人员 B 编写代码,则开发人员 A 仍然需要知道添加/删除/更新了哪些行才能在 afterEach 方法中进行恢复。
  • 你调用了peter.setParent(christian);,但是christian.setChildren(christian.getChildren().add(peter))呢?
  • 这是hibernate的特性之一。我不需要显式设置它的孩子,这意味着我不必再次保存父母。当一个孩子被保存并且我获取父母时,我会自动获取所有孩子。
猜你喜欢
  • 2011-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-08
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
相关资源
最近更新 更多