Quan hệ Many To Many cột mở rộng Hibernate với Spring Boot

Sharing now

5/5 - (6 bình chọn)

Hướng dẫn tạo quan hệ liên kết nhiều nhiều (many to many) tạo bản phụ trong hibernate spring boot

many-to-many-extracolumns (3)
Many To Many Join Extra Column Relationship Mapping with Spring Boot
Trong bài này mình sẽ hướng dẫn dùng annotation manytomany thêm bảng phụ (trung gian) trong hibernate + spring boot + mysql
Lưu ý bài này mở rộng từ bài hướng dẫn dùng annotation manytomany không thêm bảng phụ (trung gian) trong hibernate + spring boot + mysql. Nếu bạn chưa đọc thì hãy đọc trước bài đó, bài này mình sẽ không viết chi tiết như bài trước đó mà chỉ tập trung vào phần trọng tâm mở rộng thôi!
Hướng dẫn quan hệ nhiều nhiều (many to many) cột mở rộng hibernate jpa với Spring Boot Project

Tạo bảng trong cơ sở dữ liệu

Trong bài này chúng ta sẽ có 3 bảng trong cơ sở dữ liệu: book, publisher, book_publisher. Vì thế bên Java tương ứng sẽ có 3 entity là Book.javaPublisher.java và BookPublisher.java
many-to-many-extracolumns (3)
Lược đồ cơ sở dữ liệu như bạn thấy, trong bảng phụ ngoài 2 khóa chính cũng là 2 khóa ngoại như bài trước thì còn có 1 field là published_date
Mapping a many-to-many extra columns relationship with Spring Boot, Spring Data JPA and MySQL.
Database many-to-many extra columns code mysql:

CREATE DATABASE  IF NOT EXISTS `jpa_manytomany_extracolumns` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `jpa_manytomany_extracolumns`;
--
-- Table structure for table `book`
--
DROP TABLE IF EXISTS `book`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `book` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `publisher`
--
DROP TABLE IF EXISTS `publisher`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `publisher` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
--
-- Table structure for table `book_publisher`
--
DROP TABLE IF EXISTS `book_publisher`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `book_publisher` (
  `book_id` int(10) unsigned NOT NULL,
  `publisher_id` int(10) unsigned NOT NULL,
  `published_date` datetime DEFAULT NULL,
  PRIMARY KEY (`book_id`,`publisher_id`),
  KEY `fk_bookpublisher_publisher_idx` (`publisher_id`),
  CONSTRAINT `fk_bookpublisher_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `fk_bookpublisher_publisher` FOREIGN KEY (`publisher_id`) REFERENCES `publisher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

Cấu trúc project spring boot

many-to-many-extracolumns (1)
Vào phần chính nào, mình dùng Eclipse + plugin Spring suite Tool để tạo project spring boot nhanh nhất.
Tạo project Spring Starter Project như hình
many-to-many-extracolumns (2)
Sau khi tạo xong bạn sẽ thấy có class Manytomany1Application.java và file pom.xml
Nội dung file Manytomany1Application.java

package com.qlam.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Manytomany2Application  {
	public static void main(String[] args) {
		SpringApplication.run(Manytomany2Application.class, args);
	}
}

Nội dung file pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.qlam</groupId>
	<artifactId>manytomany2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>manytomany2</name>
	<description>Demo project for Spring Boot shareeverythings.com</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Khai báo các thực thể JPA Entity tức là các class trong package model

File BookPublisher.java

package com.qlam.demo.model;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "book_publisher")
public class BookPublisher implements Serializable{
	private static final long serialVersionUID = 1L;
	private Book book;
    private Publisher publisher;
    private Date publishedDate;
    @Id
    @ManyToOne
    @JoinColumn(name = "book_id")
    public Book getBook() {
        return book;
    }
    public void setBook(Book book) {
        this.book = book;
    }
    @Id
    @ManyToOne
    @JoinColumn(name = "publisher_id")
    public Publisher getPublisher() {
        return publisher;
    }
    public void setPublisher(Publisher publisher) {
        this.publisher = publisher;
    }
    @Column(name = "published_date")
    public Date getPublishedDate() {
        return publishedDate;
    }
    public void setPublishedDate(Date publishedDate) {
        this.publishedDate = publishedDate;
    }
}

File Book.java

package com.qlam.demo.model;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Book{
    private int id;
    private String name;
    private Set<BookPublisher> bookPublishers;
    public Book() {
    }
    public Book(String name) {
        this.name = name;
        bookPublishers = new HashSet<>();
    }
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    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;
    }
    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
    public Set<BookPublisher>   getBookPublishers() {
        return bookPublishers;
    }
    public void setBookPublishers(Set<BookPublisher> bookPublishers) {
        this.bookPublishers = bookPublishers;
    }
}

File Publisher.java

package com.qlam.demo.model;
import javax.persistence.*;
import java.util.Set;
@Entity
public class Publisher {
    private int id;
    private String name;
    private Set<BookPublisher> bookPublishers;
    public Publisher(){
    }
    public Publisher(String name){
        this.name = name;
    }
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    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;
    }
    @OneToMany(mappedBy = "publisher")
    public Set<BookPublisher> getBookPublishers() {
        return bookPublishers;
    }
    public void setBookPublishers(Set<BookPublisher> bookPublishers) {
        this.bookPublishers = bookPublishers;
    }
}

Tạo Spring Data JPA Repository

File BookRepository.java

package com.qlam.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.qlam.demo.model.Book;
public interface BookRepository extends JpaRepository<Book, Integer>{
}

File PublisherRepository.java

package com.qlam.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.qlam.demo.model.Publisher;
public interface PublisherRepository extends JpaRepository<Publisher, Integer> {
}

Khai báo chuỗi kết nối trong file Application Properties

Trong file application.properties thêm các đoạn code kết nối với mysql

spring.datasource.url=jdbc:mysql://localhost/jpa_manytomany_extracolumns
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

Bạn cần lưu ý 3 dòng đầu tùy chỉnh lại cho đúng với kết nối mysql của bạn.

Chạy thử chương trình

Mình sẽ tạo 2 book A,B và 2 publisher A và B. Sau đó Book A sẽ có 2 publisher là A và B, còn book B chỉ có publisher là B thôi.
Trong file Manytomany2ApplicationTests.java trong phần src/test/resources mình tạo một số đối tượng để test thử như bên dưới.

package com.qlam.demo;
import java.util.Date;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.qlam.demo.model.Book;
import com.qlam.demo.model.BookPublisher;
import com.qlam.demo.model.Publisher;
import com.qlam.demo.repository.BookRepository;
import com.qlam.demo.repository.PublisherRepository;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Manytomany2ApplicationTests {
	@Autowired
    private BookRepository bookRepository;
    @Autowired
    private PublisherRepository publisherRepository;
	@Transactional
	@Test
	public void contextLoads() {
		 Book bookA = new Book("Book A");
	        Book bookB = new Book("Book B");
	        Publisher publisherA = new Publisher("Publisher A");
	        Publisher publisherB = new Publisher("Publisher B");
	        BookPublisher bookPublisher = new BookPublisher();
	        bookPublisher.setBook(bookA);
	        bookPublisher.setPublisher(publisherA);
	        bookPublisher.setPublishedDate(new Date());
	        bookA.getBookPublishers().add(bookPublisher);
	        BookPublisher bookPublisher2 = new BookPublisher();
	        bookPublisher2.setBook(bookA);
	        bookPublisher2.setPublisher(publisherB);
	        bookPublisher2.setPublishedDate(new Date());
	        bookA.getBookPublishers().add(bookPublisher2);
	        BookPublisher bookPublisher3 = new BookPublisher();
	        bookPublisher3.setBook(bookB);
	        bookPublisher3.setPublisher(publisherB);
	        bookPublisher3.setPublishedDate(new Date());
	        bookB.getBookPublishers().add(bookPublisher3);
	        publisherRepository.save(publisherA);
	        publisherRepository.save(publisherB);
	        bookRepository.save(bookA);
	        bookRepository.save(bookB);
	        // test
	        System.out.println(bookA.getBookPublishers().size());
	}
}

Kết quả chạy, mình chỉ copy phần chính thôi!

Hibernate: insert into publisher (name) values (?)
Hibernate: insert into publisher (name) values (?)
Hibernate: insert into book (name) values (?)
Hibernate: insert into book_publisher (published_date, publisher_id, book_id) values (?, ?, ?)
Hibernate: insert into book_publisher (published_date, publisher_id, book_id) values (?, ?, ?)
Hibernate: insert into book (name) values (?)
2

Kết quả là trong bảng Book và Publisher sẽ có 2 Book và 2 Publisher tương ứng. Còn trong bảng book_publisher sẽ có 3 dòng trong đó có 1 book sẽ có 2 publisher và 1 book sẽ có 1 publisher.

Download source code

 


Sharing now

2 bình luận về “Quan hệ Many To Many cột mở rộng Hibernate với Spring Boot”

  1. Chào bạn.
    Cảm ơn bài viết rất hay. Mình thấy nhiều hướng dẫn trong trường hợp này người ta dùng @Embeddable. Bạn cho mình hỏi sự khác nhau giữa 2 cách dùng này với?

    Trả lời
    • Embeddable thường dùng để nhúng 1 cột, object vào nhiều bảng( dc tái sử dụng nhiều). Dùng trong trường hợp 1 class có 2 khóa chính (2 object), còn của mình thì chỉ trọn vẹn trong 1 object chung thôi. Trong bài mình thì bạn cũng có thể dùng Embeddable cho việc nhúng 2 Id thành 1

      Trả lời

Viết một bình luận