Many To Many Mapping In Hibernate/JPA Using Spring Boot And Oracle

In this post, we will see Many To Many Mapping In Hibernate/JPA using Spring Boot and Oracle. We will use Postman to test API. We are going to use a maven, embedded tomcat, eclipse, and oracle database.

Introduction.

Here we will have rest endpoint which will be used to save and retrieve data in the database. We have two entity Book.java and Story.java which are mapped in Many To Many relationships. In this example, we are assuming one Book can have multiple stories and one story can be associated with multiple books(Bidirectional relationships).

For this example, we are considering Book is Owner/Parent entity.

Book.java

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;

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

	@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinTable(name = "book_story", joinColumns = @JoinColumn(name = "book_id"), 
		inverseJoinColumns = @JoinColumn(name = "story_id"))
	private Set<Story> storySet = new HashSet<>();
	
	//getter & setter

}

 

Stroy.java

@Entity
public class Story {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;

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

	@ManyToMany(mappedBy = "storySet", fetch = FetchType.LAZY)
	@JsonIgnoreProperties("storySet")
	private Set<Book> bookSet = new HashSet<>() ;

}

 

We will have three rest API as below.

http://localhost:9091/book/savebook(POST) – Save the books as well as stories.
http://localhost:9091/book/books(GET)          – Returns all books.
http://localhost:9091/story/stories(GET)        – Returns all stories.

 

Table details after localhost:9091/book/savebook operation.

We have three table book, story, and book_story. We are not going to create any table, let’s hibernate do this job.


Many To Many Mapping Annotation Example In Hibernate/JPA Using Spring Boot And Oracle from scratch.

Open eclipse and create maven project, Don’t forget to check ‘Create a simple project (skip)’click on next. Fill all details(GroupId – manytomanyhibernatejpa, ArtifactId – manytomanyhibernatejpa and name – manytomanyhibernatejpa) and click on finish. Keep packaging as the jar.

Modify the pom.xml.

<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>manytomanyhibernatejpa</groupId>
	<artifactId>manytomanyhibernatejpa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>manytomanyhibernatejpa</name>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>

		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>

			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<fork>true</fork>
					<executable>C:\Program Files\Java\jdk1.8.0_131\bin\javac.exe</executable>
				</configuration>
			</plugin>


		</plugins>
	</build>
</project>

Note – In pom.xml we have defined javac.exe path in configuration tag. You need to change accordingly i.e where you have installed JDK.

If you see any error for oracle dependency then follow these steps. Let maven download all necessary jar. Once it is done we will able to see the maven dependency folder which contains different jar files. We can start writing our controller classes, ServiceImpl and Repository. The directory structure of the application looks as below.

Many To Many Mapping In Hibernate/JPA using Spring Boot

Book.java

package com.hibernatejpa.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;

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

	@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinTable(name = "book_story", joinColumns = @JoinColumn(name = "book_id"), 
		inverseJoinColumns = @JoinColumn(name = "story_id"))
	private Set<Story> storySet = new HashSet<>();

	public int getId() {
		return id;
	}

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

	public String getBookName() {
		return bookName;
	}

	public void setBookName(String bookName) {
		this.bookName = bookName;
	}

	public Set<Story> getStorySet() {
		return storySet;
	}

	public void setStorySet(Set<Story> storySet) {
		this.storySet = storySet;
	}

}

Story.java

package com.hibernatejpa.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
public class Story {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;

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

	@ManyToMany(mappedBy = "storySet", fetch = FetchType.LAZY)
	@JsonIgnoreProperties("storySet")
	private Set<Book> bookSet = new HashSet<>() ;

	public int getId() {
		return id;
	}

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

	public Set<Book> getBookSet() {
		return bookSet;
	}

	public void setBookSet(Set<Book> bookSet) {
		this.bookSet = bookSet;
	}

	public String getStoryName() {
		return storyName;
	}

	public void setStoryName(String storyName) {
		this.storyName = storyName;
	}

}

 

Define the repository interface extending CrudRepository.

BookRepository.java

package com.hibernatejpa.repository;

import java.io.Serializable;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.hibernatejpa.entity.Book;

@Repository
public interface BookRepository extends CrudRepository<Book, Serializable> {

}

StoryRepository.java

package com.hibernatejpa.repository;

import java.io.Serializable;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.hibernatejpa.entity.Story;

@Repository
public interface StoryRepository extends CrudRepository<Story, Serializable> {
	
}

Define the Service interface.

BookService.java


package com.hibernatejpa.service;

import java.util.List;

import org.springframework.stereotype.Component;

import com.hibernatejpa.entity.Book;

@Component
public interface BookService {
	public List<Book> saveBook(List<Book> bookList);

}

Define service implementation class.

BookServiceImpl.java

package com.hibernatejpa.impl;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hibernatejpa.entity.Book;
import com.hibernatejpa.entity.Story;
import com.hibernatejpa.repository.BookRepository;
import com.hibernatejpa.service.BookService;

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	public List<Book> saveBook(List<Book> bookList) {

		// Use two books which we are passing from postman
		Book book1 = bookList.get(0);
		Book book2 = bookList.get(1);

		// create stories
		Story story1 = new Story();
		Story story2 = new Story();
		story1.setStoryName("Story name 1");
		story2.setStoryName("Story name 2");

		// populate storySet
		book1.getStorySet().add(story1);
		book1.getStorySet().add(story2);
		book2.getStorySet().add(story1);
		book2.getStorySet().add(story2);

		// populate bookSet
		story1.getBookSet().add(book1);
		story1.getBookSet().add(book2);
		story2.getBookSet().add(book1);
		story2.getBookSet().add(book2);

		// Save owner entities
		List<Book> bookResponse = (List<Book>) bookRepository.saveAll(bookList);

		return bookResponse;

	}

	public Book findByBookId(int bookId) {
		Optional<Book> bookResponse = bookRepository.findById(bookId);
		Book book = bookResponse.get();
		return book;
	}

}

Note – See here more about @Component, @Controller, @Service and @Repository annotations here.


BookController.java

package com.hibernatejpa.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.hibernatejpa.entity.Book;
import com.hibernatejpa.repository.BookRepository;
import com.hibernatejpa.service.BookService;
 
@RestController
@RequestMapping(value = "/book")
public class BookController {
	
	@Autowired
	private BookService bookService;
	
	@Autowired
	private BookRepository bookRepository;
	
	
	@RequestMapping(value = "/savebook",method = RequestMethod.POST)
	@ResponseBody
    public List<Book> saveBook(@RequestBody List<Book> bookList) {
		List<Book> bookResponse = (List<Book>) bookService.saveBook(bookList);
		return bookResponse;
	}
	
	@ResponseBody
	@RequestMapping(value = "/books", method = RequestMethod.GET)
	public List<Book> getBookDetails() {
		List<Book> bookResponse = (List<Book>) bookRepository.findAll();

		return bookResponse;
	}
	
	
}

StoryController.java

package com.hibernatejpa.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.hibernatejpa.entity.Story;
import com.hibernatejpa.repository.StoryRepository;
@RestController
@RequestMapping(value = "/story")
public class StoryController {
	
	@Autowired
	StoryRepository storyRepository;
	
	@ResponseBody
	@RequestMapping(value = "/stories", method = RequestMethod.GET)
	public List<Story> getStoryDetails() {
		List<Story> storyresponse = (List<Story>) storyRepository.findAll();

		return storyresponse;
	}
}

Note – See more details about @Controller and RestController here.

JpaConfig.java

package com.hibernatejpa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories(basePackages = "com.hibernatejpa.repository")
public class JpaConfig {

}

Note – See more details about @Configuration annotations here.

Define the SpringMain.java

package com.hibernatejpa.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages="com.hibernatejpa.*")
@EntityScan("com.hibernatejpa.*")
public class SpringMain {
	public static void main(String[] args) {

        SpringApplication.run(SpringMain.class, args);
    }

}

Note – See more details about @ComponentScan here.

And finally, we have an application.properties file where we have database details.

# Connection url for the database
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=SYSTEM
spring.datasource.password=oracle2
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# Show or not log for each sql query
spring.jpa.show-sql = true
 
 
spring.jpa.hibernate.ddl-auto =create
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
 
server.port = 9091

Let’s run the SpringMain class and test the save URL.

Testing the endpoint Using Postman.

Request data for –  http://localhost:9091/book/savebook(POST)

[

	{
		"bookName": "Book1"
	},
	{
		"bookName": "Book2"
	}

]

Response Data.

[
    {
        "id": 1,
        "bookName": "Book1",
        "storySet": [
            {
                "id": 2,
                "storyName": "Story name 1",
                "bookSet": [
                    {
                        "id": 4,
                        "bookName": "Book2"
                    },
                    {
                        "id": 1,
                        "bookName": "Book1"
                    }
                ]
            },
            {
                "id": 3,
                "storyName": "Story name 2",
                "bookSet": [
                    {
                        "id": 4,
                        "bookName": "Book2"
                    },
                    {
                        "id": 1,
                        "bookName": "Book1"
                    }
                ]
            }
        ]
    },
    {
        "id": 4,
        "bookName": "Book2",
        "storySet": [
            {
                "id": 2,
                "storyName": "Story name 1",
                "bookSet": [
                    {
                        "id": 4,
                        "bookName": "Book2"
                    },
                    {
                        "id": 1,
                        "bookName": "Book1"
                    }
                ]
            },
            {
                "id": 3,
                "storyName": "Story name 2",
                "bookSet": [
                    {
                        "id": 4,
                        "bookName": "Book2"
                    },
                    {
                        "id": 1,
                        "bookName": "Book1"
                    }
                ]
            }
        ]
    }
]

 

 

Observe the console. Below query generated by the hibernate.

Hibernate: insert into book (book_name, id) values (?, ?)
Hibernate: insert into story (story_name, id) values (?, ?)
Hibernate: insert into story (story_name, id) values (?, ?)
Hibernate: insert into book (book_name, id) values (?, ?)
Hibernate: insert into book_story (book_id, story_id) values (?, ?)
Hibernate: insert into book_story (book_id, story_id) values (?, ?)
Hibernate: insert into book_story (book_id, story_id) values (?, ?)
Hibernate: insert into book_story (book_id, story_id) values (?, ?)

 

 

Response Data for – http://localhost:9091/book/books

[
    {
        "bookId": 1,
        "bookName": "Book1",
        "storySet": [
            {
                "storyId": 4,
                "storyName": "Story name 2"
            },
            {
                "storyId": 2,
                "storyName": "Story name 1"
            }
        ]
    },
    {
        "bookId": 3,
        "bookName": "Book2",
        "storySet": [
            {
                "storyId": 4,
                "storyName": "Story name 2"
            },
            {
                "storyId": 2,
                "storyName": "Story name 1"
            }
        ]
    }
]

Response Data for – http://localhost:9091/story/stories

[
    {
        "storyId": 2,
        "storyName": "Story name 1",
        "bookSet": [
            {
                "bookId": 1,
                "bookName": "Book1"
            },
            {
                "bookId": 3,
                "bookName": "Book2"
            }
        ]
    },
    {
        "storyId": 4,
        "storyName": "Story name 2",
        "bookSet": [
            {
                "bookId": 1,
                "bookName": "Book1"
            },
            {
                "bookId": 3,
                "bookName": "Book2"
            }
        ]
    }
]

That’s all about Many To Many Mapping In Hibernate/JPA using Spring Boot.

Note – Default Fetch type in case of below annotations.

@OneToOne – Default fetch type is EAGER.
@OneToMany – Default fetch type is LAZY.
@ManyToOne – Default fetch type is EAGER.
@ManyToMany – Default fetch type is LAZY.

You may like.

One to One Mapping Annotation Example in Hibernate/JPA using Spring Boot and Oracle.
One to One Bidirectional Mapping Example In Hibernate/JPA Using Spring Boot and Oracle.
One To Many Mapping Annotation Example In Hibernate/JPA Using Spring Boot And Oracle.
One To Many Bidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle.
Many To One Unidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle.

Inheritance Mapping in Hibernate Using Spring Boot and Oracle.

@ManyToMany docs.

 

Summary – We have seen Many To Many Mapping In Hibernate/JPA using Spring Boot.