Создание простых связей типа многие-к-одному в моделях предметной области полностью соответствует взаимоотношению таблиц в БД. Строки одной из таблиц содержат внешний ключ, ссылающийся на строку другой таблицы. При этом та таблица, на которую ссылаются, об этом «не знает». Во многих случаях в модели предметной области воспроизведение подобных отношений между классами-сущностями оказывается достаточным.
Подготовка
Создадим минимальное Spring Boot приложение с поддержкой JPA/Hibernate.
Код
Создадим класс предметной области следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Entity public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; //Конструкторы, геттеры и сеттеры, equals(), hashCode() и т.д. } |
Класс Person не ссылается ни на какие другие классы предметной области. И не содержит никаких сведений о том, ссылается ли какой-то класс на него.
Создадим следующий класс предметной области, ссылающийся на предыдущий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Entity public class Address { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "PERSON_ID", nullable = false) private Person person; @Column(nullable = false) private String city; @Column(nullable = false) private String street; @Column(nullable = false) private String house; //Конструкторы, геттеры и сеттеры, equals(), hashCode() и т.д. } |
Аннотация @ManyToOne над полем Person person указывает Hibernate’у создать связь вида многие-к-одному между сущностями Address и Person. С точки зрения БД, в таблице ADDRESS будет создано поле с внешним ключом под названием PERSON_ID (так как мы ссылаемся на таблицу PERSON, в которой первичный ключ — колонка ID). Аннотация @ManyToOne — единственное, что требуется для установления связи многие-к-одному.
Использование аннотации @JoinColumn носит необязательный характер. Но в ней можно переопределить название колонки внешнего ключа (мы оставили его таким же, как если бы Hibernate создал его автоматически), а также добавить этой колонке некоторые ограничения. В нашем примере мы делаем её NOT NULL колонкой, из-за чего связь между адресом и персоной становится обязательной (но могла бы таковой и не быть).
Создадим репозиторий для сущности Person:
1 2 3 |
@Repository public interface PersonRepository extends JpaRepository<Person, Long> { } |
Создадим репозиторий для сущности Address:
1 2 3 4 |
@Repository public interface AddressRepository extends JpaRepository<Address, Long> { List<Address> findByPerson(Person person); } |
Напишем тестовый метод, который продемонстрирует работу настроек:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@SpringBootTest class TestApplicationTests { @Autowired PersonRepository personRepository; @Autowired AddressRepository addressRepository; @Test void manyToOneTest() { Person irina = new Person("irina", 28); List<Address> irinasAddresses = List.of( new Address(irina, "Москва", "Садовая", "12"), new Address(irina, "Воскресенск", "СНТ Строитель", "424") ); personRepository.save(irina); addressRepository.saveAll(irinasAddresses); List<Address> addressesInDb = addressRepository.findByPerson(irina); assertTrue(addressesInDb.containsAll(irinasAddresses)); } } |
Мы создали один объект irina типа Person и список из двух объектов типа Address. Создавая объекты Address, мы заполнили поле person ссылкой на объект irina. Затем сохранили все созданные объекты. Так как и Person, и Address являются классами-сущностями, то они сами управляют своим жизненным циклом.
Затем мы вызываем метод addressRepository.findByPerson(irina), с помощью которого ищем все строки таблицы ADDRESS, соответствующие созданному ранее объекту irina. И действительно, такие строки находятся, что показывает нам, что Hibernate правильно установил связи многие-к-одному между классами Address и Person — в БД на одну строку таблицы PERSON ссылается несколько строк таблицы ADDRESS. И имея данные строки таблицы PERSON можно найти соответствующие строки таблицы ADDRESS.