gpt4 book ai didi

hibernate - JOIN FETCH 查询未在 Spring DataJpaTest 中获取惰性集合

转载 作者:行者123 更新时间:2023-12-03 23:51:07 24 4
gpt4 key购买 nike

背景

你好!我正在尝试编写一个测试来检查 JOIN FETCH 查询是否正确获取惰性集合。我在一个简单的 Spring Boot 2.1.7 项目中尝试这个,h2 有数据源,并且加载了 spring-boot-starter-data-jpa。测试是用 Junit4 和 assertJ 进行的,我认为这并不重要。

当我使用 @DataJpaTest ,集合在这里返回空,而不是例如@SpringBootTest ,我不明白为什么。

实体和存储库

我有两个简单的实体,ClassroomPerson .一个教室可以容纳多人。这是在类里面定义的:

    @OneToMany(mappedBy = "classroom", fetch = FetchType.LAZY)
private Set<Person> persons = new HashSet<>();

在人员类中:

    @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "classroom_id")
private Classroom classroom;

ClassRoomRepository我已经定义了一种方法,应该急切地在教室里找人:

    @Query("SELECT DISTINCT c FROM Classroom c JOIN FETCH c.persons WHERE c.id = :classroomId")
Classroom getClassRoom(@Param("classroomId") Long classRoomId);

考试
@RunWith(SpringRunner.class)
@DataJpaTest
public class ClassroomTest{
@Autowired
private PersonRepository personRepository;

@Autowired
private ClassRoomRepository classRoomRepository;

@Test
public void lazyCollectionTest() {
Classroom classroom = new Classroom();
classRoomRepository.save(classroom);

Person person = new Person(classroom);
personRepository.save(person);

assertThat(classRoomRepository.getClassRoom(classroom.getId()).getPersons()).hasSize(1);
}
}

测试结果

我看到的是 getPersons()返回:
  • 如果测试类使用
  • 注释,则为 0

    @DataJpaTest
  • 1 如果测试类注释为:

  • @DataJpaTest
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
  • 1 如果测试类注释为:

  • @SpringBootTest

    结论/问题

    我知道 @DataJpaTest在最后回滚的事务中运行每个测试。但是为什么这会阻止这个连接获取查询返回数据呢?

    最佳答案

    这是 时的预期行为。双向关联不同步 .
    整个测试的 1 个事务:1 个持久性上下文
    使用 @DataJpaTest 时单独,测试方法执行 1 个事务,1 个持久性上下文, 1个一级缓存 , 因为 @Transactional适用于所有测试方法。
    在这种情况下,第一个 classroomclassRoomRepository.getClassRoom(classroom.getId()) 返回的那个是同一个实例,因为 Hibernate 使用它的一级缓存返回教室实例,它是用一个空 Set 构造的,忽略 ResultSet 记录 从您的查询。
    可以验证:

    Classroom classroom = new Classroom(); // constructs the Classroom with an empty Set
    classRoomRepository.save(classroom);
    Classroom classroom2 = classRoomRepository.getClassRoom(classroom.getId());
    System.out.println("same? " + (classroom==classroom2));
    // output: same? true
    // and classroom2.persons is empty :)
    修复:双向关联同步
    由于 Hibernate 忽略了您的查询结果, @OneToMany查询后仍为空。
    换句话说,您“忘记”了在 Classroom.persons 中添加人员。
    您必须在 setter 和 adders(或任何操纵这些关联的方法,包括您的构造函数)中手动同步双向关联,或者使用带有 enableAssociationManagement 的 Hibernate 字节码增强(很神奇,但要小心使用)。
    让我们写一个(流利的) Classroom.addPerson在此教室中添加人员并更新人员的加法器:
    public Classroom addPerson(Person person) {
    this.persons.add(person);
    person.setClassroom(this);
    return this;
    }
    请注意,您还应该添加 Classroom.removePerson方法,设置 Person.classroomnull从集合中移除此人后。
    然后重写您的测试以使其通过:
    Classroom classroom = new Classroom();
    classRoomRepository.save(classroom);

    Person person = new Person();
    classroom.addPerson(person);
    personRepository.save(person);
    在这种情况下,您手动将人员添加到集合中并保持关联的另一端同步,这是一种自然的做事方式。
    但是如果你想坚持你的 Person(Classroom classroom)构造函数:
    public Person(Classroom classroom) {
    classroom.addPerson(this); // add this person to the classroom
    }
    如果您希望能够以两种方式操作此关联,您还可以使用 Person.setClassroom二传手,但它有点重:
    public Person setClassroom(Classroom classroom) {
    this.classroom = classroom;
    if(classroom != null)
    this.classroom.getPersons().add(this);
    else
    this.classroom.getPersons().remove(this);
    return this;
    }
    您手动使关联的双方保持同步,因此您不依赖 Hibernate 获取集合。
    您的测试将通过,我添加了一个检查以确保关联是同步的:
    Classroom classroom = new Classroom();
    classRoomRepository.save(classroom);

    Person person = new Person(classroom);

    // check that the classroom contains the person
    Assertions.assertThat(classroom.getPersons().contains(person)).isTrue();

    personRepository.save(person);

    Assertions.assertThat(classRoomRepository.getClassRoom(classroom.getId())
    .getPersons()).hasSize(1);
    但请记住,调用 classRoomRepository.getClassRoom(classroom.getId())是无用的,因为如果结果已经存在于持久性上下文中,Hibernate 会忽略结果。
    你应该只使用你的第一个 classroom实例。
    多个事务:多个持久性上下文
    当您添加 @Transactional(propagation = Propagation.NOT_SUPPORTED) ,您选择不使用事务,因此使用了 3 个事务、3 个持久性上下文和 3 个一级缓存(一个用于第一次保存,一个用于第二个,一个用于查询)。 @SpringBootTest 相同不使用 @Transactional一点也不。
    在这两种情况下,您正在处理不同的实例,因此 Hibernates 使用查询中的 ResultSet 记录为您的教室提供获取的人员,正如预期的那样。
    System.out.println("same? " + (classroom==classroom2));
    // output: same? false
    有关更多信息,请查看本文以及弗拉德对爱迪生问题的回答:
    https://vladmihalcea.com/jpa-hibernate-first-level-cache/

    If there’s already a managed entity with the same id, then theResultSet record is ignored.


    您也可以查看 https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/关于双向关联同步。
    如果您对 Hibernate 字节码增强和神奇的双向关联同步感兴趣,请阅读 https://docs.jboss.org/hibernate/orm/current/topical/html_single/bytecode/BytecodeEnhancement.html

    关于hibernate - JOIN FETCH 查询未在 Spring DataJpaTest 中获取惰性集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57767530/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com