У меня следующая настройка: два объекта (родительский, дочерний), дочерние элементы должны быть лениво загружены в родительский элемент, который отлично работает, расширяя JpaRepository
с помощью настраиваемого репозитория ParentCustomRepository
и вызывая 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;
}
}
Пока все хорошо. Однако я не могу заставить тесты работать приемлемым способом. Я хочу иметь тесты, которые независимы друг от друга таким образом, чтобы не было необходимости в fre sh контексте для каждого теста. Вот простой тестовый класс, который показывает, что я имею в виду.
@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
сохраняется изменения в базе данных, что приводит к другим результатам в остальных тестах. - Я знаю, что могу просто очищать базу данных после каждого теста с помощью метода tearDown, помеченного
@AfterEach
. Это приведет к чистому началу каждого теста и кажется вполне подходящим для этого примера. Однако приложение, которое я разрабатываю для своей компании, зависит от гораздо большей инициализированной базы данных, что потребует довольно много времени для сотен тестов. (Это также тот случай, почему я не хочу использовать @DirtiesContext
где-нибудь в тестах)
- Если я запускаю эти тесты с аннотация
@Transactional
, откат выполняется после каждого теста. Это именно то поведение, которое я ищу. Как видно из результатов теста, некоторые тесты не проходят. Это связано с тем, что childRepository.save(peter)
не будет сохранять добавленный дочерний элемент и, следовательно, вызов `parentRepository.findAllWithChildren () вернет только дочерние элементы, которые находятся в базе данных с самого начала. - Я знаю, что могу просто сохранить транзакцию через фиксацию, но тогда у меня больше не будет независимых тестов.
- Использование
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
в тестовом классе не работает. Поскольку я не хочу использовать этот уровень изоляции в реальном программном коде, я не проводил дальнейших исследований в этом направлении.
Я предполагаю, что мне нужно a commit
, прежде чем я смог получить доступ к обновленным отношениям между родителем и его дочерними элементами. Коммит также очищает все точки сохранения, и, следовательно, я не могу выполнить откат даже с настраиваемым управлением транзакциями. из вас уже сталкивались с подобными проблемами и могли бы предложить решение или объяснение. Есть ли какие-то передовые методы, о которых я не знаю?
Alex