OneToMany Mapping using @JoinTable in Hibernate/JPA

In this post, We will see OneToMany bidirectional mapping using @JoinTable example in Hibernate/JPA. We will see how to use @JoinTable with OneToMany bidirectional mapping. We are going to use Spring Boot, Oracle database, and postman(for testing). We have a separate tutorial for OneToMany bidirectional mapping without using @JoinTable.

Many To Many @JoinTable Example In Hibernate/JPA Using Spring Boot And Oracle.

Note – For this tutorial, we are not going to create the table manually. Let’s hibernate do this job.

Introduction.

We have two entity Book.java and Story.java. Both entities are mapped into bidirectional mapping. Consider Book entity is the parent entity and Story entity is a child entity.

Book.Java(Parent)

@Entity
public class Book {

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

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

	@OneToMany(cascade = CascadeType.ALL, mappedBy = "book", fetch = FetchType.EAGER)
	private List<Story> storyList = new ArrayList<>();

}

Story.java(Child)

package com.hibernatejpa.entity;

@Entity
public class Story {

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

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

	@ManyToOne(fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
	@JoinTable(name = "book_story", joinColumns = @JoinColumn(name = "story_id", referencedColumnName = "id", nullable = true), 
		inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id", nullable = true))
	@JsonIgnoreProperties("storyList")
	private Book book;

}

Using @JoinTable and @JoinColumn we can define the table name and column name. For example, in the above code snippet, we have defined book_story table. The book_story table contains two columns book_id and story_id which we have defined using @JoinColumns annotation.

Table details.

When we use @JoinTable we will have a separate table(let’s say book_story in our case). There will be three tables book, story and book_story(Define using @JoinTable).

OneToMany Mapping using @JoinTable in Hibernate/JPA

We will have rest API to save the record to the database.

Rest endpoints for this example.

1. Save book(POST) – http://localhost:9091/book/savebook – Observ in below response one book have many stories.

Request data

{
“bookName”:”Premchand’s stories”
}

Response Data

{
	"id": 1,
	"bookName": "Premchand's stories",
	"storyList": [{
			"id": 2,
			"storyName": "Push Ki Rat",
			"book": {
				"id": 1,
				"bookName": "Premchand's stories"
			}
		},
		{
			"id": 3,
			"storyName": "Idgah",
			"book": {
				"id": 1,
				"bookName": "Premchand's stories"
			}
		},
		{
			"id": 4,
			"storyName": "Story Of Two Oxes",
			"book": {
				"id": 1,
				"bookName": "Premchand's stories"
			}
		}
	]
}
OneToMany Mapping using @JoinTable in Hibernate/JPA using Spring Boot and Oracle

2. Get the book (GET) – http://localhost:9091/book/{bookId}- Get book details.

@JoinTable annotation using Spring Boot

OneToMany Mapping using @JoinTable in Hibernate/JPA, Spring Boot and Oracle.

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

If you see any error for oracle dependency then follow these steps.

<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>onetomanyjointable</groupId>
	<artifactId>onetomanyjointable</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>onetomanyjointable</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.

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.

Book.java

package com.hibernatejpa.entity;

import java.util.ArrayList;
import java.util.List;

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.OneToMany;

@Entity
public class Book {

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

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

	@OneToMany(cascade = CascadeType.ALL, mappedBy = "book", fetch = FetchType.EAGER)
	private List<Story> storyList = new ArrayList<>();

	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 List<Story> getStoryList() {
		return storyList;
	}

	public void setStoryList(List<Story> storyList) {
		this.storyList = storyList;
	}

}

Story.java

package com.hibernatejpa.entity;

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.ManyToOne;

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;

	@ManyToOne(fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
	@JoinTable(name = "book_story", joinColumns = @JoinColumn(name = "story_id", referencedColumnName = "id", nullable = true), inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id", nullable = true))
	@JsonIgnoreProperties("storyList")
	private Book book;

	public int getId() {
		return id;
	}

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

	public Book getBook() {
		return book;
	}

	public void setBook(Book book) {
		this.book = book;
	}

	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.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.hibernatejpa.entity.Book;

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

}

Define the Service interface.

BookService.java

package com.hibernatejpa.service;

import org.springframework.stereotype.Component;

import com.hibernatejpa.entity.Book;

@Component
public interface BookService {
	public Book saveBook(Book book);
	public Book findByBookId(int bookId);
}

Define service implementation class.

BookServiceImpl.java

package com.hibernatejpa.impl;

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

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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;

	@PersistenceContext
	private EntityManager entityManager;

	@Transactional
	public Book saveBook(Book book) {
		List<Story> storyList = new ArrayList<>();

		// create first story
		Story story1 = new Story();
		story1.setStoryName("Push Ki Rat");

		// create second story
		Story story2 = new Story();
		story2.setStoryName("Idgah");

		// create third story
		Story story3 = new Story();
		story3.setStoryName("Story Of Two Oxes");

		// add all story into storyList. Till here we have prepared data for OneToMany
		storyList.add(story1);
		storyList.add(story2);
		storyList.add(story3);

		// Prepare data for ManyToOne
		story1.setBook(book);
		story2.setBook(book);
		story3.setBook(book);

		book.setStoryList(storyList);
		book = bookRepository.save(book);

		return book;

	}

	@Transactional
	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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
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.service.BookService;

@RestController
@RequestMapping(value = "/book")
public class BookController {

	@Autowired
	private BookService bookService;

	@RequestMapping(value = "/savebook", method = RequestMethod.POST)
	@ResponseBody
	public Book saveBook(@RequestBody Book book) {
		Book bookResponse = bookService.saveBook(book);
		return bookResponse;
	}

	@RequestMapping(value = "/{bookId}", method = RequestMethod.GET)
	@ResponseBody
	public Book getBookDetails(@PathVariable int bookId) {
		Book bookResponse = bookService.findByBookId(bookId);

		return bookResponse;
	}

}

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 {

}

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);
    }

}

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 endpoints.

http://localhost:9091/book/savebook
http://localhost:9091/book/{bookId}

Generated Query while save and get operation.

Below Query Generated while the saving operation.

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

Below Query generated while the get operation.

Hibernate: select book0_.id as id1_0_0_, book0_.book_name as book_name2_0_0_, storylist1_.book_id as book_id1_1_1_,
story2_.id as story_id2_1_1_, story2_.id as id1_2_2_, story2_.story_name as story_name2_2_2_,
story2_1_.book_id as book_id1_1_2_
from book book0_ left outer join book_story storylist1_ on book0_.id=storylist1_.book_id
left outer join story story2_ on storylist1_.story_id=story2_.id
left outer join book_story story2_1_ on story2_.id=story2_1_.story_id where book0_.id=?

Some basic points about @JoinTable.

  • @JoinTable annotation is available in javax.persistence package and introduced in JPA 1.0.
  • This annotation can be used with the getter method or field.
  • Some important attributes of @JoinTable annotation are name, joinColumns, inverseJoinColumns etc.

That’s all about OneToMany Mapping using @JoinTable in Hibernate/JPA Using Spring Boot and Oracle.

Download source code from github.

More hibernate/JPA association mapping examples.

One To One unidirectional mapping example.
One To One Bidirectional mapping example.
One To Many unidirectional mapping example.
Many To one unidirectional mapping example.

Spring Data JPA example.

See Hibernate Docs.