Перейти к содержанию

Spring Boot: сопоставление «один ко многим» в Hibernate и JPA

В этом руководстве узнаем, как реализовать однонаправленные отношения в ассоциации «один ко многим».

Что такое ассоциация «один ко многим» (One-To-Many)

Рассмотрим пример: John – владелец (Owner) популярного блога, и на этом сайте есть большое количество блогов (Blogs) на разные темы. Итак, здесь один владелец является автором многих блогов. Например, в одном издательстве опубликованы книги многих авторов или в одном блоге много отзывов. Таким образом, в отношениях «один ко многим» один объект имеет отношение со списком/набором объектов.

Взгляните на пример ассоциации владельца с блогами:

// Blog class
public class Blog {
	private Int id;
	…
	private String title;
	…
}

// Owner class
public class Owner {
	private Int id;
	…
	private List<Blog> blogs; 
	…
}

Важно обратить внимание на объявление переменной blogs в классе Owner. Поскольку Owner опубликовал несколько блогов, класс Owner должен состоять из списка блогов. Таким образом, Many (List) Blogs связан с одним владельцем (Owner) – это ассоциация «Один ко многим».

Сначала реализуем однонаправленное отношение в ассоциации «один ко многим», затем рассмотрим двунаправленное отношение.

Создание проекта Spring Boot

Чтобы создать новый проект Spring Boot, будем использовать Spring Initializr, который создаст базовую структуру проекта Spring Boot. Были добавлены следующие зависимости:

  • Spring Boot DevTools – необходимые инструменты разработки;
  • Spring Web – для Spring MVC и встроенного Tomcat, который будет запускать приложение Spring Boot;
  • Spring Data JPA – Java Persistence API;
  • MySQL Driver – драйвер JDBC для MySQL (для другой БД необходимо выбрать соответствующую зависимость).

Затем нажмите GENERATE, чтобы загрузить zip-файл проекта. Разархивируйте zip-файл. Теперь импортируйте проект в Eclipse/STS как проект Maven.

Подключение к базе данных

Поскольку используем MySQL в качестве базы данных, поместим информацию о подключении в файл application.properties в пару имя/значение, чтобы Hibernate использовал эту информацию для подключения к базе данных. Следующий фрагмент описывает информацию о соединении:

spring.datasource.url=jdbc:mysql://172.17.0.2:3306/spring_boot_one_to_many?useSSL=false
spring.datasource.username=root
spring.datasource.password=admin@123

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

Здесь установили datasource.url на URL-адрес соединения JDBC. datasource.user и datasource.password – это учетные данные для базы данных.

Указывать datasource.driver-class-name не требуется, поскольку Spring Boot может собирать необходимую информацию из URL-адреса подключения. Но мы будем в безопасности, если укажем driver-class-name.

jpa.show-sql используется для отображения запросов Hibernate sql в консоли, когда приложение запущено, jpa.hibernate.ddl-auto настроен на обновление (будет обновлять схему базы данных каждый раз, когда перезапускаем приложение), а hibernate.dialect указывает, какой диалект базы данных используем.

Чтобы продемонстрировать однонаправленную связь в ассоциации «один ко многим», создаем две сущности: Owner и Blog. Используя аннотацию @OneToMany, устанавливаем отношение «один ко многим» между этими двумя объектами.

Сущности (Entities)

Сначала создадим класс Blog с аннотациями, чтобы сделать его персистентным.

@Entity
@Table(name = "BLOG_DETAILS")
public class Blog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private int id;

    @Column(name = "title")
    private String title;

    @Column(name = "category")
    private String category;

    @Column(name = "content")
    private String content;

    public Blog() {
    }

    public Blog(String title, String category, String content) {
        this.title = title;
        this.category = category;
        this.content = content;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", category='" + category + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

Класс Blog имеет некоторые private-свойства с геттером и сеттером, а также параметризованный конструктор и переопределенный метод toString().

Теперь класс Owner:

@Entity
@Table(name = "OWNER_DETAILS")
public class Owner {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private int id;

    @Column(name = "name")
    private String name;

    @Column(name = "email")
    private String email;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "blog_id")
    private List<Blog> blogList;

    public Owner() {
    }

    public Owner(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public List<Blog> getBlogList() {
        return blogList;
    }

    public void setBlogList(List<Blog> blogList) {
        this.blogList = blogList;
    }

    @Override
    public String toString() {
        return "Owner{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", blogList=" + blogList +
                '}';
    }
}

Так как у одного владельца много блогов, то мы объявили список блогов с именем переменной blogList. Аннотация @OneToMany инструктирует Hibernate, чтобы он установил отношение «один ко многим» между Owner и Blog и объединил их по имени столбца blog_id. Таким образом, Hibernate создаст две таблицы – OWNER_DETAILS и BLOG_DETAILS (имя таблицы указано в соответствующих POJO) и изменит таблицу BLOG_DETAILS, чтобы добавить внешний ключ (имя столбца blog_id), который будет ссылаться на OWNER_DETAILS (id имени столбца).

FetchType.LAZY сначала загрузит основной объект (Owner), а затем по запросу загрузит связанный объект (Blog).

Репозитории

В Spring Boot репозиторий – это уровень доступа к данным для взаимодействия с реальной базой данных для таких операций, как вставка, обновление, удаление с использованием Spring Data JPA. Поскольку репозитории расширяют возможности JpaRepository, мы значительно сократили количество стандартного кода, необходимого для выполнения операций с базой данных.

Вот наш BlogRepository:

public interface BlogRepository extends JpaRepository<Blog, Integer> {
}

И OwnerRepository:

public interface OwnerRepository extends JpaRepository<Owner,Integer> {
}

Controller

Controller – это общедоступный класс с одним или несколькими общедоступными методами. По соглашению контроллеры помещаются в каталог Controller. В Spring Boot, если класс аннотирован с помощью @Controller или @RestController, этот конкретный класс будет выполнять роль контроллера, а его общедоступные методы предоставляются как конечные точки HTTP, поскольку они аннотируются с помощью @PostMapping или @GetMapping.

Таким образом, HTTP-запрос GET к http://localhost:{PORT}/{method-name} вызывает выполнение метода @GetMapping класса ExampleController. OwnerController состоит из двух методов: один для сохранения Owner и Blog, другой – для получения сведений об Owner по id. Используя аннотацию @Autowired, мы внедрили OwnerRepository в контроллер. Код выглядит следующим образом:

@RestController
@RequestMapping("/owner")
public class OwnerController {

    @Autowired
    private OwnerRepository ownerRepository;

    @PostMapping("/saveOwner")
    public Owner saveOwner(@RequestBody Owner owner) {
        System.out.println("Owner save called...");
        Owner ownerOut = ownerRepository.save(owner);
        System.out.println("Saved!!!");

        return ownerOut;
    }

    @GetMapping("/getOwner/{id}")
    public String getOwner(@PathVariable(name = "id") String id) {
        System.out.println("Owner get called...");
        Owner ownerOut = ownerRepository.getById(Integer.valueOf(id));
        System.out.println("\nOwner details with Blogs :: \n" + ownerOut);
        System.out.println("\nList of Blogs :: \n" + ownerOut.getBlogList());
        System.out.println("\nDone!!!");

        return "Owner fetched...";
    }

}

Запуск проекта

Чтобы запустить проект в Visual Studio Code, выполните следующие действия:

  1. Откройте SpringbootmanytomanyApplication.java.
  2. Нажмите Run, чтобы запустить программу Java.

Чтобы запустить проект в Eclipse, выполните следующие действия:

  1. Щелкните правой кнопкой мыши SpringbootmanytomanyApplication.java.
  2. Выберите Run As, затем нажмите Spring Boot App.

Теперь взгляните на логи в консоли (код Eclipse/VS): как видите, Hibernate создал две таблицы – BLOG_DETAILS и OWNER_DETAILS, а также изменил таблицу BLOG_DETAILS, чтобы добавить внешний ключ ограничения (owner_id) для установки ссылки с помощью OWNER_DETAILS (ID) таблицы. Итак, мы успешно сопоставили объекты Java в таблицах базы данных.

Поскольку не указали порт в application.properties, проект будет работать на 8080, а URL-адрес по умолчанию для доступа к любому REST API этого проекта – http://localhost:8080/. Поскольку проект работает на локальной машине, мы использовали localhost. Если проект выполняется на каком-то удаленном сервере или в EC2, необходимо использовать IP-адрес удаленного сервера или EC2 или динамический IP-адрес.

Тестирование REST API

Теперь протестируем те REST API, которые создали в OwnerController.

Создание нового владельца

Чтобы создать нового владельца вместе с его/ее блогами, вызываем следующий URL-адрес в Postman:

http://localhost:8080/owner/saveOwner

Также смотрим логи в консоли:

Тут сообщается, что Hibernate сначала вставляет владельца в таблицу OWNER_DETAILS, затем вставляет блог в таблицу BLOG_DETAILS, а затем устанавливает значение столбца ID таблицы OWNER_DETAILS в столбцах blog_id таблицы BLOG_DETAILS.

Посмотрим на таблицу OWNER_DETAILS:

И на таблицу BLOG_DEAILS:

Выбор владельца

Чтобы получить определенного владельца вместе с его/ее блогами, вызываем следующий URL-адрес в Postman – мы передали id владельца как 1:

http://localhost:8080/owner/getOwner/1

Логи в консоли:

В итоге получили список блогов владельца.

Заключение

Мы успешно создали отношение «один ко многим».

Скачать исходный код можно здесь.

Оригинал