Ter*_*sen 4 hibernate spring-boot
你好!我正在尝试编写一个测试来检查 JOIN FETCH 查询是否正确获取惰性集合。我正在一个简单的 Spring Boot 2.1.7 项目中尝试这个,其中 h2 有数据源,并且加载了 spring-boot-starter-data-jpa 。测试是使用 Junit4 和 assertJ 进行的,我认为这并不重要。
当我使用时@DataJpaTest,集合在这里返回空,而不是例如@SpringBootTest,我不明白为什么。
我有两个简单的实体,Classroom和Person。一个教室可以容纳多人。这是在课堂上定义的:
@OneToMany(mappedBy = "classroom", fetch = FetchType.LAZY)
private Set<Person> persons = new HashSet<>();
Run Code Online (Sandbox Code Playgroud)
在 person 类中:
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "classroom_id")
private Classroom classroom;
Run Code Online (Sandbox Code Playgroud)
在 中ClassRoomRepository,我定义了一个应该急切地获取教室中的人员的方法:
@Query("SELECT DISTINCT c FROM Classroom c JOIN FETCH c.persons WHERE c.id = :classroomId")
Classroom getClassRoom(@Param("classroomId") Long classRoomId);
Run Code Online (Sandbox Code Playgroud)
@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);
}
}
Run Code Online (Sandbox Code Playgroud)
我看到的是getPersons()返回:
@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);
}
}
Run Code Online (Sandbox Code Playgroud)
@DataJpaTest
Run Code Online (Sandbox Code Playgroud)
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
Run Code Online (Sandbox Code Playgroud)
我知道@DataJpaTest在事务中运行每个测试并在最后回滚。但是为什么这会阻止此连接获取查询返回数据呢?
这是双向关联不同步时的预期行为。
\n整个测试 1 个事务:1 个持久化上下文
\n单独使用时@DataJpaTest,测试方法执行时有 1 个事务、1 个持久化上下文、1 个一级缓存,因为@Transactional应用于所有测试方法。
在这种情况下,第一个classroom和返回的实例classRoomRepository.getClassRoom(classroom.getId())是相同的实例,因为 Hibernate 使用其一级缓存返回教室实例,该实例是用空 Set 构造的,并忽略查询中的ResultSet 记录。\n它可以是已验证:
Classroom classroom = new Classroom(); // constructs the Classroom with an empty Set\nclassRoomRepository.save(classroom);\nClassroom classroom2 = classRoomRepository.getClassRoom(classroom.getId());\nSystem.out.println("same? " + (classroom==classroom2));\n// output: same? true\n// and classroom2.persons is empty :)\nRun Code Online (Sandbox Code Playgroud)\n修复:双向关联同步
\n由于 Hibernate 忽略了您的查询结果,因此@OneToMany查询后 仍然是空的。\n换句话说,您“忘记”在 Classroom.persons 中添加该人。
您必须在设置器和加法器(或任何操作这些关联的方法,包括您的构造函数)中手动同步双向关联,或者使用带有enableAssociationManagement 的Hibernate 字节码增强(很神奇,但要小心使用)。
\n让我们编写一个(流畅的)Classroom.addPerson加法器,在这个 Classroom 中添加一个 Person,并更新该 Person:
public Classroom addPerson(Person person) {\n this.persons.add(person);\n person.setClassroom(this);\n return this;\n}\nRun Code Online (Sandbox Code Playgroud)\n请注意,您还应该添加一个Classroom.removePerson方法,该方法在从集合中删除人员后设置Person.classroom为。null
然后重写你的测试以使其通过:
\nClassroom classroom = new Classroom();\nclassRoomRepository.save(classroom);\n\nPerson person = new Person();\nclassroom.addPerson(person);\npersonRepository.save(person);\nRun Code Online (Sandbox Code Playgroud)\n在这种情况下,您手动将人员添加到集合中并保持关联的另一端同步,这是一种自然的处理方式。
\n但如果你想坚持使用你的Person(Classroom classroom)构造函数:
public Person(Classroom classroom) {\n classroom.addPerson(this); // add this person to the classroom\n}\nRun Code Online (Sandbox Code Playgroud)\n如果您希望能够以两种方式操纵此关联,您还可以使用Person.setClassroom但它有点重:
public Person setClassroom(Classroom classroom) {\n this.classroom = classroom;\n if(classroom != null)\n this.classroom.getPersons().add(this);\n else\n this.classroom.getPersons().remove(this);\n return this;\n}\nRun Code Online (Sandbox Code Playgroud)\n您手动使关联的双方保持同步,因此您不依赖 Hibernate 来获取集合。
\n您的测试将通过,我添加了一项检查以确保关联同步:
\nClassroom classroom = new Classroom();\nclassRoomRepository.save(classroom);\n\nPerson person = new Person(classroom);\n\n// check that the classroom contains the person\nAssertions.assertThat(classroom.getPersons().contains(person)).isTrue();\n\npersonRepository.save(person);\n\nAssertions.assertThat(classRoomRepository.getClassRoom(classroom.getId())\n .getPersons()).hasSize(1);\nRun Code Online (Sandbox Code Playgroud)\n但请记住,对 的调用classRoomRepository.getClassRoom(classroom.getId())是无用的,因为如果结果已存在于持久性上下文中,Hibernate 会忽略结果。\n您应该只使用第一个classroom实例。
多个事务:多个持久化上下文
\n当您添加 时@Transactional(propagation = Propagation.NOT_SUPPORTED),您选择不使用事务,因此使用了 3 个事务、3 个持久化上下文和 3 个一级缓存(一个用于第一次保存,一个用于第二次,一个用于查询)。\n@SpringBootTest相同不使用@Transactional根本
在这两种情况下,您正在操作不同的实例,因此 Hibernates 使用查询中的 ResultSet 记录为您的教室提供所获取的人员,如预期的那样。
\nSystem.out.println("same? " + (classroom==classroom2));\n// output: same? false\nRun Code Online (Sandbox Code Playgroud)\n有关更多信息,请查看本文以及 Vlad 对 Edison 问题的回答:\n https://vladmihalcea.com/jpa-hibernate-first-level-cache/
\n\n\n如果\xe2\x80\x99 已存在具有相同 ID 的托管实体,则\nResultSet 记录将被忽略。
\n
您还可以检查https://vladmihalcea.com/jpa-hibernate-synchronize-bidirection-entity-associations/有关双向关联同步的信息。
\n如果您对 Hibernate 字节码增强和神奇的双向关联同步感兴趣,请阅读https://docs.jboss.org/hibernate/orm/current/topical/html_single/bytecode/BytecodeEnhancement.html
\n| 归档时间: |
|
| 查看次数: |
1589 次 |
| 最近记录: |