Microservices with Modules(JPMS)

 

				
					<!-- pom.xml in 6392_chapter2 -->
<?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.packtpub.mmj</groupId>
    <artifactId>6392_chapter2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>lib</module>
        <module>rest</module>
    </modules>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>9</maven.compiler.source>
        <maven.compiler.target>9</maven.compiler.target>
        <spring-boot-version>2.0.0.BUILD-SNAPSHOT</spring-boot-version>
        <spring-version>5.0.0.BUILD-SNAPSHOT</spring-version>
        <start-class>com.packtpub.mmj.rest.RestSampleApp</start-class>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.BUILD-SNAPSHOT</version>
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.pactkpub.mmj</groupId>
                <artifactId>rest</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.packtpub.mmj</groupId>
                <artifactId>lib</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.0.BUILD-SNAPSHOT</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                        <configuration>
                            <classifier>exec</classifier>
                            <mainClass>${start-class}</mainClass>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.9</source>
                    <target>1.9</target>
                    <showDeprecation>true</showDeprecation>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>
				
			
				
					<!-- pom.xml in lib -->
<?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>
    <parent>
        <groupId>com.packtpub.mmj</groupId>
        <artifactId>6392_chapter2</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>lib</artifactId>
    <packaging>jar</packaging>
    <properties>
        <exec.mainClass>com.packtpub.mmj.lib.Lib</exec.mainClass>
    </properties>
</project>
				
			
				
					// module-info.java in lib module
module com.packtpub.mmj.lib {
    exports com.packtpub.mmj.lib.model to com.packtpub.mmj.rest;
    opens com.packtpub.mmj.lib.model;
}
				
			
				
					// Calculation.java
package com.packtpub.mmj.lib.model;

import java.util.List;

public class Calculation {
    
    String function;
    private List<String> input;
    private List<String> output;
    
    public Calculation(){
        
    }
    public Calculation(List<String> input, List<String> output, String function) {
        this.function = function;
        this.input = input;
        this.output = output;
    }
    public List<String> getInput() {
        return input;
    }
    public void setInput(List<String> input) {
        this.input = input;
    }
    public List<String> getOutput() {
        return output;
    }
    public void setOutput(List<String> output) {
        this.output = output;
    }
    public String getFunction() {
        return function;
    }
    public void setFunction(String function) {
        this.function = function;
    }    
}

				
			
				
					<!-- pom.xml in rest -->
<?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>
    <parent>
        <groupId>com.packtpub.mmj</groupId>
        <artifactId>6392_chapter2</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>rest</artifactId>
    <packaging>jar</packaging>
    <properties>
        <exec.mainClass>com.packtpub.mmj.rest.Rest</exec.mainClass>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.packtpub.mmj</groupId>
            <artifactId>lib</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
				
			
				
					// module-info.java in rest module
module com.packtpub.mmj.rest {
    requires spring.core;
    requires spring.beans;
    requires spring.context;
    requires spring.aop;
    requires spring.web;
    requires spring.expression;

    requires spring.boot;
    requires spring.boot.autoconfigure;

    requires com.packtpub.mmj.lib;

    exports com.packtpub.mmj.rest;
    exports com.packtpub.mmj.rest.resources;

    opens com.packtpub.mmj.rest;
    opens com.packtpub.mmj.rest.resources;
}

				
			
				
					// CalculationController.java
package com.packtpub.mmj.rest.resources;

import com.packtpub.mmj.lib.model.Calculation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("calculation")
public class CalculationController {
    private static final String PATTERN = "^-?+\\d+\\.?+\\d*$";

    @RequestMapping("/power")
    public Calculation pow(@RequestParam(value = "base") String b, @RequestParam(value = "exponent") String e) {
        List<String> input = new ArrayList();
        input.add(b);
        input.add(e);
        List<String> output = new ArrayList();
        String powValue;
        if (b != null && e != null && b.matches(PATTERN) && e.matches(PATTERN)) {
            powValue = String.valueOf(Math.pow(Double.valueOf(b), Double.valueOf(e)));
        } else {
            powValue = "Base or/and Exponent is/are not set to numeric value.";
        }
        output.add(powValue);
        return new Calculation(input, output, "power");
    }

    @RequestMapping(value = "/sqrt/{value:.+}", method = RequestMethod.GET)
    public Calculation sqrt(@PathVariable(value = "value") String aValue) {
        List<String> input = new ArrayList<>();
        input.add(aValue);
        List<String> output = new ArrayList();
        String sqrtValue;
        if (aValue != null && aValue.matches(PATTERN)) {
            sqrtValue = String.valueOf(Math.sqrt(Double.valueOf(aValue)));
        } else {
            sqrtValue = "Input value is not set to numeric value.";
        }
        output.add(sqrtValue);
        return new Calculation(input, output, "sqrt");
    }
}

				
			
				
					//RestSampleApp.java
package com.packtpub.mmj.rest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestSampleApp {
     public static void main(String[] args) {
         SpringApplication.run(RestSampleApp.class, args);
    }
}

				
			
				
					> mvn clean install
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for 6392_chapter2 1.0-SNAPSHOT:
[INFO] 
[INFO] 6392_chapter2 ...................................... SUCCESS [  0.725 s]
[INFO] lib ................................................ SUCCESS [  1.216 s]
[INFO] rest ............................................... SUCCESS [  0.806 s]

				
			
				
					//While using JDK 16 and above, we get an error similar to this
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @198e2867
        at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:387) ~[na:na]
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:363) ~[na:na]
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:311) ~[na:na]
        at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:201) ~[na:na]
        at java.base/java.lang.reflect.Method.setAccessible(Method.java:195) ~[na:na]
        at org.springframework.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:569) ~[na:na]
        at org.springframework.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:52) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
        at org.springframework.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:243) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
        at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:329) ~[spring-core-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
        ... 33 common frames omitted
				
			
				
					// use this code to run without the above errors
java --add-opens java.base/java.lang=ALL-UNNAMED -jar rest/target/rest-1.0-SNAPSHOT-exec.jar
				
			
				
					// run the code on different port
java --add-opens java.base/java.lang=ALL-UNNAMED -Dserver.port=8083 -jar rest/target/rest-1.0-SNAPSHOT-exec.jar
				
			

http://localhost:8083/calculation/power?base=2&exponent=3

http://localhost:8083/calculation/sqrt/2

 

Criteria Query with Join

Using Join with Criteria queries

				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 100 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'GE23451');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);

insert into student_course(student_id, course_id)
values(20001, 10001);
insert into student_course(student_id, course_id)
values(20002, 10001);
insert into student_course(student_id, course_id)
values(20003, 10001);
insert into student_course(student_id, course_id)
values(20001, 10003);



				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String name;
	
	@Setter(AccessLevel.NONE)
	@OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
	private List<Review> reviews = new ArrayList<>();
	
	@ManyToMany(mappedBy = "courses")
	@Setter(value = AccessLevel.NONE)
	private List<Student> students = new ArrayList<>();
	
	public void addStudent(Student student) {
		this.students.add(student);
	}
	
	public void addReview(Review review) {
		this.reviews.add(review);
	}
	
	public void removeReview(Review review) {
		this.reviews.remove(review);
	}
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

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

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
	
	@ToString.Exclude
	@ManyToMany
	@Setter(value = AccessLevel.NONE)
	@JoinTable(name = "STUDENT_COURSE", 
			joinColumns = @JoinColumn(name = "STUDENT_ID"),
			inverseJoinColumns = @JoinColumn(name = "COURSE_ID")
			)
	private List<Course> courses = new ArrayList<>();
	
	public void addCourse(Course course) {
		this.courses.add(course);
	}
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
	
	@ToString.Exclude
	@OneToOne(fetch = FetchType.LAZY, mappedBy = "passport")
	private Student student;
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class CriteriaJoinQueryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	@Test
	@Transactional
	void join() {
		//"Select * From Course c join c.students s"
		// 1. Use Criteria builder to create CriteriaQuery returning the expected result
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Course> cq = cb.createQuery(Course.class);
		
		// 2. Define root for the tables which are involved in the query
		Root<Course> courseRoot = cq.from(Course.class); // 'From Course c' part of main query
		
		// 3. Define Predicates for adding conditions
		Join<Object, Object> join = courseRoot.join("students");

		// 4. Add predicate to criteria query
		
		// 5. Build the typed query using entity manager and criteria query
		TypedQuery<Course> query = em.createQuery(cq.select(courseRoot));
		List<Course> resultList = query.getResultList();
		logger.info("join");
		logger.info("Size: -> {}", resultList.size());
		logger.info("Courses: -> {}", resultList);
	}
	
	@Test
	@Transactional
	void left_join() {
		//"Select * From Course c left join c.students s"
		// 1. Use Criteria builder to create CriteriaQuery returning the expected result
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Course> cq = cb.createQuery(Course.class);
		
		// 2. Define root for the tables which are involved in the query
		Root<Course> courseRoot = cq.from(Course.class); // 'From Course c' part of main query
		
		// 3. Define Predicates for adding conditions
		Join<Object, Object> join = courseRoot.join("students", JoinType.LEFT);

		// 4. Add predicate to criteria query
		
		// 5. Build the typed query using entity manager and criteria query
		TypedQuery<Course> query = em.createQuery(cq.select(courseRoot));
		List<Course> resultList = query.getResultList();
		logger.info("left_join");
		logger.info("Size: -> {}", resultList.size());
		logger.info("Courses: -> {}", resultList);
	}
}

				
			
				
					2023-03-06T18:29:25.574+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : join
2023-03-06T18:29:25.574+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : Size: -> 2
2023-03-06T18:29:25.574+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : Courses: -> [Course(id=10001, name=JPA in 5 Steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-06T18:29:25.317624, createdDate=2023-03-06T18:29:25.317624), Course(id=10003, name=JPQL in 50 Steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-06T18:29:25.319045, createdDate=2023-03-06T18:29:25.319045)]
2023-03-06T18:29:25.608+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : left_join
2023-03-06T18:29:25.608+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : Size: -> 3
2023-03-06T18:29:25.608+05:30  INFO 81680 --- [           main] c.b.j.h.j.CriteriaJoinQueryTest          : Courses: -> [Course(id=10001, name=JPA in 5 Steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-06T18:29:25.317624, createdDate=2023-03-06T18:29:25.317624), Course(id=10002, name=JDBC in 100 Steps, reviews=[], students=[], lastUpdatedDate=2023-03-06T18:29:25.318861, createdDate=2023-03-06T18:29:25.318861), Course(id=10003, name=JPQL in 50 Steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-06T18:29:25.319045, createdDate=2023-03-06T18:29:25.319045)]

				
			

Related Tutorials

Criteria Queries

Use of Criteria queries instead of writing the SQL queries

				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 100 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 Steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'GE23451');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);

insert into student_course(student_id, course_id)
values(20001, 10001);
insert into student_course(student_id, course_id)
values(20002, 10001);
insert into student_course(student_id, course_id)
values(20003, 10001);
insert into student_course(student_id, course_id)
values(20001, 10003);



				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String name;
	
	@Setter(AccessLevel.NONE)
	@OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
	private List<Review> reviews = new ArrayList<>();
	
	@ManyToMany(mappedBy = "courses")
	@Setter(value = AccessLevel.NONE)
	private List<Student> students = new ArrayList<>();
	
	public void addStudent(Student student) {
		this.students.add(student);
	}
	
	public void addReview(Review review) {
		this.reviews.add(review);
	}
	
	public void removeReview(Review review) {
		this.reviews.remove(review);
	}
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class CriteriaQueryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	@Test
	@Transactional
	void criteria_query_get_all_courses() {
		//"Select * From Course c"
		// 1. Use Criteria builder to create CriteriaQuery returning the expected result
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Course> cq = cb.createQuery(Course.class);
		
		// 2. Define root for the tables which are involved in the query
		Root<Course> courseRoot = cq.from(Course.class); // 'From Course c' part of main query
		
		// 3. Define Predicates for adding conditions
		
//		TypedQuery<Course> createQuery = em.createQuery("select c from Course c", Course.class);
		TypedQuery<Course> createQuery = em.createQuery(cq.select(courseRoot));
		List<Course> resultList = createQuery.getResultList();
		logger.info("criteria_query_get_all_courses");
		logger.info("Size: -> {}", resultList.size());
		logger.info("Courses: -> {}", resultList);
	}
	
	@Test
	@Transactional
	void criteria_query_all_courses_with_100Steps() {
		//"Select * From Course c where name like '%100 Steps%'"
		// 1. Use Criteria builder to create CriteriaQuery returning the expected result
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Course> cq = cb.createQuery(Course.class);
		
		// 2. Define root for the tables which are involved in the query
		Root<Course> courseRoot = cq.from(Course.class); // 'From Course c' part of main query
		
		// 3. Define Predicates for adding conditions
		Predicate like100Steps = cb.like(courseRoot.get("name"), "%100 Steps"); // 'like '%100' part of main query
		
		// 4. Add predicate to criteria query
		cq.where(like100Steps); // 'where' part of main query
		
//		TypedQuery<Course> createQuery = em.createQuery("select c from Course c", Course.class);
		TypedQuery<Course> createQuery = em.createQuery(cq.select(courseRoot));
		List<Course> resultList = createQuery.getResultList();
		logger.info("criteria_query_all_courses_with_100Steps");
		logger.info("Size: -> {}", resultList.size());
		logger.info("Courses: -> {}", resultList);
	}
	
	@Test
	@Transactional
	void all_courses_without_students() {
		// "Select * from Course c where c.students is empty"
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Course> cq = cb.createQuery(Course.class);
		Root<Course> courseRoot = cq.from(Course.class);
		
		Predicate emptyStudents = cb.isEmpty(courseRoot.get("students"));
		cq.where(emptyStudents);
		TypedQuery<Course> query = em.createQuery(cq.select(courseRoot));
		List<Course> resultList = query.getResultList();
		
		logger.info("all_courses_without_students");
		logger.info("Size: -> {}", resultList.size());
		logger.info("Courses: -> {}", resultList);
	}
}

				
			
				
					2023-03-06T17:14:20.343+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : criteria_query_get_all_courses
2023-03-06T17:14:20.344+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Size: -> 3
2023-03-06T17:14:20.344+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Courses: -> [Course(id=10001, name=JPA in 5 Steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-06T17:14:19.983050, createdDate=2023-03-06T17:14:19.983050), Course(id=10002, name=JDBC in 100 Steps, reviews=[], students=[], lastUpdatedDate=2023-03-06T17:14:19.984303, createdDate=2023-03-06T17:14:19.984303), Course(id=10003, name=JPQL in 50 Steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-06T17:14:19.984514, createdDate=2023-03-06T17:14:19.984514)]

				
			
				
					2023-03-06T17:14:20.313+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : criteria_query_all_courses_with_100Steps
2023-03-06T17:14:20.313+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Size: -> 1
2023-03-06T17:14:20.313+05:30  INFO 69425 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Courses: -> [Course(id=10002, name=JDBC in 100 Steps, reviews=[], students=[], lastUpdatedDate=2023-03-06T17:14:19.984303, createdDate=2023-03-06T17:14:19.984303)]

				
			
				
					2023-03-06T17:37:24.958+05:30  INFO 73453 --- [           main] c.b.j.h.j.CriteriaQueryTest              : all_courses_without_students
2023-03-06T17:37:24.958+05:30  INFO 73453 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Size: -> 1
2023-03-06T17:37:24.958+05:30  INFO 73453 --- [           main] c.b.j.h.j.CriteriaQueryTest              : Courses: -> [Course(id=10002, name=JDBC in 100 Steps, reviews=[], students=[], lastUpdatedDate=2023-03-06T17:37:24.683421, createdDate=2023-03-06T17:37:24.683421)]

				
			

Related Tutorials

JOIN Query

Different types of SQL Joins


				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'GE23451');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);

insert into student_course(student_id, course_id)
values(20001, 10001);
insert into student_course(student_id, course_id)
values(20002, 10001);
insert into student_course(student_id, course_id)
values(20003, 10001);
insert into student_course(student_id, course_id)
values(20001, 10003);



				
			

JOIN Types

Join Course with Student

JOIN => Select c, s from Course c JOIN c.students s
(INNER) JOIN : Returns records that have matching values in both tables.

LEFT JOIN => Select c, s from Course c LEFT JOIN c.students s
LEFT (OUTER) JOIN: Returns all records from the left table, and the matched records from the right table.

CROSS JOIN = > 3 Course X 3 Student => 9 Result Takes all

(INNER) Join
				
					@Test
	@Transactional
	public void join() {
		Query query = em.createQuery("Select c, s from Course c JOIN c.students s");
		List<Object[]> list = query.getResultList();
		logger.info("List size: -> {}", list.size());
		
		for(Object[] result: list) {
			logger.info("Course: {} Student: {}", result[0], result[1]);
		}
	}
				
			
				
					2023-03-01T20:20:41.461+05:30  INFO 384446 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : List size: -> 4
2023-03-01T20:20:41.461+05:30  INFO 384446 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:20:40.706694, createdDate=2023-03-01T20:20:40.706694) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
2023-03-01T20:20:41.489+05:30  INFO 384446 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:20:40.706694, createdDate=2023-03-01T20:20:40.706694) Student: Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345))
2023-03-01T20:20:41.490+05:30  INFO 384446 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:20:40.706694, createdDate=2023-03-01T20:20:40.706694) Student: Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))
2023-03-01T20:20:41.490+05:30  INFO 384446 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10003, name=JPQL in 50 steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-01T20:20:40.709764, createdDate=2023-03-01T20:20:40.709764) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
				
			
LEFT Join
				
					@Test
	@Transactional
	public void left_join() {
		Query query = em.createQuery("Select c, s from Course c LEFT JOIN c.students s");
		List<Object[]> list = query.getResultList();
		logger.info("List size: -> {}", list.size());
		
		for(Object[] result: list) {
			logger.info("Course: {} Student: {}", result[0], result[1]);
		}
	}
				
			
				
					2023-03-01T20:22:31.038+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : List size: -> 5
2023-03-01T20:22:31.038+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:22:30.396516, createdDate=2023-03-01T20:22:30.396516) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
2023-03-01T20:22:31.071+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:22:30.396516, createdDate=2023-03-01T20:22:30.396516) Student: Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345))
2023-03-01T20:22:31.071+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-01T20:22:30.396516, createdDate=2023-03-01T20:22:30.396516) Student: Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))
2023-03-01T20:22:31.071+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10002, name=JDBC in 10 steps, reviews=[], students=[], lastUpdatedDate=2023-03-01T20:22:30.398139, createdDate=2023-03-01T20:22:30.398139) Student: null
2023-03-01T20:22:31.072+05:30  INFO 384824 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10003, name=JPQL in 50 steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-01T20:22:30.398423, createdDate=2023-03-01T20:22:30.398423) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))

				
			
Cross Join
				
					@Test
	@Transactional
	public void cross_join() {
		Query query = em.createQuery("Select c, s from Course c, Student s");
		List<Object[]> list = query.getResultList();
		logger.info("List size: -> {}", list.size());
		
		for(Object[] result: list) {
			logger.info("Course: {} Student: {}", result[0], result[1]);
		}
	}
				
			
				
					2023-03-03T16:21:01.511+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : List size: -> 9
2023-03-03T16:21:01.511+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-03T16:21:00.807627, createdDate=2023-03-03T16:21:00.807627) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
2023-03-03T16:21:01.541+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-03T16:21:00.807627, createdDate=2023-03-03T16:21:00.807627) Student: Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345))
2023-03-03T16:21:01.541+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345)), Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345)), Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))], lastUpdatedDate=2023-03-03T16:21:00.807627, createdDate=2023-03-03T16:21:00.807627) Student: Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))
2023-03-03T16:21:01.541+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10002, name=JDBC in 10 steps, reviews=[], students=[], lastUpdatedDate=2023-03-03T16:21:00.810388, createdDate=2023-03-03T16:21:00.810388) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
2023-03-03T16:21:01.542+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10002, name=JDBC in 10 steps, reviews=[], students=[], lastUpdatedDate=2023-03-03T16:21:00.810388, createdDate=2023-03-03T16:21:00.810388) Student: Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345))
2023-03-03T16:21:01.543+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10002, name=JDBC in 10 steps, reviews=[], students=[], lastUpdatedDate=2023-03-03T16:21:00.810388, createdDate=2023-03-03T16:21:00.810388) Student: Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))
2023-03-03T16:21:01.543+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10003, name=JPQL in 50 steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-03T16:21:00.810832, createdDate=2023-03-03T16:21:00.810832) Student: Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))
2023-03-03T16:21:01.545+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10003, name=JPQL in 50 steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-03T16:21:00.810832, createdDate=2023-03-03T16:21:00.810832) Student: Student(id=20002, name=Adam, passport=Passport(id=40002, number=F212345))
2023-03-03T16:21:01.545+05:30  INFO 93909 --- [           main] c.b.j.h.jpahibernatedemo.JPQLJoins       : Course: Course(id=10003, name=JPQL in 50 steps, reviews=[Review(id=50004, rating=5, description=Good you are writing)], students=[Student(id=20001, name=Ranga, passport=Passport(id=40001, number=E12345))], lastUpdatedDate=2023-03-03T16:21:00.810832, createdDate=2023-03-03T16:21:00.810832) Student: Student(id=20003, name=Jane, passport=Passport(id=40003, number=GE23451))

				
			

Related Tutorials

JPQL Queries

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			
				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String name;
	
	@Setter(AccessLevel.NONE)
	@OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
	private List<Review> reviews = new ArrayList<>();
	
	@ManyToMany(mappedBy = "courses")
	@Setter(value = AccessLevel.NONE)
	private List<Student> students = new ArrayList<>();
	
	public void addStudent(Student student) {
		this.students.add(student);
	}
	
	public void addReview(Review review) {
		this.reviews.add(review);
	}
	
	public void removeReview(Review review) {
		this.reviews.remove(review);
	}
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

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

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
	
	@ToString.Exclude
	@ManyToMany
	@Setter(value = AccessLevel.NONE)
	@JoinTable(name = "STUDENT_COURSE", 
			joinColumns = @JoinColumn(name = "STUDENT_ID"),
			inverseJoinColumns = @JoinColumn(name = "COURSE_ID")
			)
	private List<Course> courses = new ArrayList<>();
	
	public void addCourse(Course course) {
		this.courses.add(course);
	}
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
	
	@ToString.Exclude
	@OneToOne(fetch = FetchType.LAZY, mappedBy = "passport")
	private Student student;
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Review {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String rating;
	
	@NonNull
	private String description;
	
	@ToString.Exclude
	@ManyToOne
	private Course course;
}

				
			
				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'GE23451');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);

insert into student_course(student_id, course_id)
values(20001, 10001);
insert into student_course(student_id, course_id)
values(20002, 10001);
insert into student_course(student_id, course_id)
values(20003, 10001);
insert into student_course(student_id, course_id)
values(20001, 10003);



				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class JPQLQueryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	// get course which do not have any student
//	@Test
	@Transactional
	void jpql_courses_without_students() {
		TypedQuery<Course> createQuery = em.createQuery("select c from Course c where c.students is empty", Course.class);
		List<Course> resultList = createQuery.getResultList();
		logger.info("Courses: -> {}", resultList);
	}
	
	// get courses where which has more thatn 2 students
//	@Test
	@Transactional
	void jpql_courses_with_atleast_2_students() {
		TypedQuery<Course> createQuery = em.createQuery("select c from Course c where size(c.students) > 2", Course.class);
		List<Course> resultList = createQuery.getResultList();
		logger.info("Courses: -> {}", resultList);
	}
	
	// get courses order by student size
	// by default, order by is ascending(0-10)
//	@Test
	@Transactional
	void jpql_courses_ordered_by_students() {
		TypedQuery<Course> createQuery = em.createQuery("select c from Course c order by size(c.students) desc", Course.class);
		List<Course> resultList = createQuery.getResultList();
		logger.info("Courses: -> {}", resultList);
	}
	
	// get students whose passport number contains 12345
	/**
	 * Other functions
	 * ===============
	 * like
	 * Between 100 and 500
	 * Is Null
	 * 
	 * ---- On String ------
	 * upper, lower, trim, length
	 */
	@Test
	@Transactional
	void jpql_sudent_passport_matching() {
		TypedQuery<Student> createQuery = em.createQuery("select s from Student s where s.passport.number like '%1234%'", Student.class);
		List<Student> resultList = createQuery.getResultList();
		logger.info("Courses: -> {}", resultList);
	}

}

				
			

Related Tutorials

JPA Inheritance Hierarchies and Mapping

SINGLE_TABLE inheritance

Single_Table – With Inheritance, all the classes are mapped to single table.
It is the default strategy.
One Employee table showing entries of PartTime and FullTime employees.


Benefits:,

High performance, but lots of null data, issues of data integrity.

				
					@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Employee_Type")
public abstract class Employee {
				
			

TABLE_PER_CLASS inheritance

Table_Per_Class – creates table per concrete entity class.
Table for each of PartTime and FullTime employees.

 :

Benefits:

High performance, but lots of duplicate data as multiple fields are common between the tables.

				
					@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Employee {
				
			

JOINED inheritance

Join – Will create table per each entity.
3 Table each of Employee, PartTime and FullTime employees.

 

Join query result:

1.SELECT * FROM EMPLOYEE , PART_TIME_EMPLOYEE where PART_TIME_EMPLOYEE.ID = EMPLOYEE.ID

2. SELECT * FROM EMPLOYEE , FULL_TIME_EMPLOYEE where FULL_TIME_EMPLOYEE.ID = EMPLOYEE.ID

Benefits:

Performance not best as it has to join multiple tables, but good in terms of database design as there is no duplication of columns

				
					@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Employee {
				
			

Complete source

				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RequiredArgsConstructor
@AllArgsConstructor
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Employee {
	
	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.math.BigDecimal;

import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@AllArgsConstructor
public class FullTimeEmployee extends Employee {

	private BigDecimal salary;
	
	public FullTimeEmployee(String name, BigDecimal salary) {
		super(name);
		this.salary = salary;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.math.BigDecimal;

import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@AllArgsConstructor
public class PartTimeEmployee extends Employee {

	private BigDecimal hourlyWage;
	
	public PartTimeEmployee(String name, BigDecimal hourlyWage) {
		super(name);
		this.hourlyWage = hourlyWage;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Employee;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class EmployeeRepository {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	public void insertEmployee(Employee employee) {
		em.persist(employee);
	}
	
	public List<Employee> retrieveAllEmployee(){
		TypedQuery<Employee> query = em.createQuery("select e from Employee e", Employee.class);
		return query.getResultList();
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.math.BigDecimal;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.FullTimeEmployee;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.PartTimeEmployee;
import com.brains.jpa.hibernate.jpahibernatedemo.repository.EmployeeRepository;

@SpringBootApplication
public class JpaHibernateDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaHibernateDemoApplication.class, args);
	}

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private EmployeeRepository employeeRepository;
	
	@Override
	public void run(String... args) throws Exception {
		
		employeeRepository.insertEmployee(new FullTimeEmployee("Jack", new BigDecimal(10000)));
		employeeRepository.insertEmployee(new PartTimeEmployee("Jill", new BigDecimal(50)));
		logger.info("All employees: -> {}", employeeRepository.retrieveAllEmployee());
	}
}
				
			
				
					All employees: -> [Employee(id=1, name=Jack), Employee(id=2, name=Jill)]

				
			

Related Tutorials

@ManyToMany Mapping

We use ManyToMany when Multiple relations are between tables.

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			
				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			
				
					SELECT * FROM STUDENT_COURSE, STUDENT, COURSE
where STUDENT_COURSE.student_id = STUDENT.id
and STUDENT_COURSE.course_id = COURSE.id
				
			

One Course has many students and One Student can enroll many courses.

 

  • @Setter(value = AccessLevel.NONE) – This removes the setter method
  • @JoinTable(name = "STUDENT_COURSE)" – Give a custom name to the join table
  • joinColumns = @JoinColumn(name = "STUDENT_ID") – Give a custom name to the owning column
  • inverseJoinColumns = @JoinColumn(name = "COURSE_ID") – Give a custom name to the referenced column
  • mappedBy is always added to the non-owning side of the relationship.
Here between Student and Course, Student is the owner, that’s the reason mappedBy is added in the Course.
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

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

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
	
	@ToString.Exclude
	@ManyToMany
	@Setter(value = AccessLevel.NONE)
	@JoinTable(name = "STUDENT_COURSE", 
			joinColumns = @JoinColumn(name = "STUDENT_ID"),
			inverseJoinColumns = @JoinColumn(name = "COURSE_ID")
			)
	private List<Course> courses = new ArrayList<>();
	
	public void addCourse(Course course) {
		this.courses.add(course);
	}
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String name;
	
	@Setter(AccessLevel.NONE)
	@OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
	private List<Review> reviews = new ArrayList<>();
	
	@ManyToMany(mappedBy = "courses")
	@Setter(value = AccessLevel.NONE)
	private List<Student> students = new ArrayList<>();
	
	public void addStudent(Student student) {
		this.students.add(student);
	}
	
	public void addReview(Review review) {
		this.reviews.add(review);
	}
	
	public void removeReview(Review review) {
		this.reviews.remove(review);
	}
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
	
	@ToString.Exclude
	@OneToOne(fetch = FetchType.LAZY, mappedBy = "passport")
	private Student student;
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Review {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String rating;
	
	@NonNull
	private String description;
	
	@ToString.Exclude
	@ManyToOne
	private Course course;
}

				
			
				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'G12345');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);

insert into student_course(student_id, course_id)
values(20001, 10001);
insert into student_course(student_id, course_id)
values(20002, 10001);
insert into student_course(student_id, course_id)
values(20003, 10001);
insert into student_course(student_id, course_id)
values(20001, 10003);



				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Passport;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class StudentRepository {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	public Student findById(Long id) {
		return em.find(Student.class, id);
	}
	
	public void saveStudentWithPassport() {
		Passport passport = new Passport("Z12345");
		em.persist(passport);
		Student student1 = new Student("Mike");
		student1.setPassport(passport);
		passport.setStudent(student1);
		em.persist(student1);
	}
	
	public void someOperationToUnderstandPersistanceContext() {
		Student student = em.find(Student.class, 20001L);
		logger.info("Student -> {}", student);
		
		Passport passport = student.getPassport();
		logger.info("Passport -> {}", passport);
		
		passport.setNumber("E12345-0");
		student.setName("Ranga - updated");
		logger.info("Student -> {}", student);
	}
	
	public void insertHardcodedStudentCourse() {
		Student student = new Student("Jack");
		Course course = new Course("Microservices in 100 Steps");
		em.persist(student);
		em.persist(course);
		
		student.addCourse(course);
		course.addStudent(student);
	}
	
	public void insertStudentCourse(Student student, Course course) {
		em.persist(student);
		em.persist(course);
		
		student.addCourse(course);
		course.addStudent(student);
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.JpaHibernateDemoApplication;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class StudentCourseRepositoryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	@Test
	@Transactional
	void retrieveStudentAndCourse() {
		Student student = em.find(Student.class, 20001L);
		logger.info("Student: -> {}", student);
		logger.info("Courses: -> {}", student.getCourses());
	}
	
	@Test
	@Transactional
	void retrieveCourseAndStudent() {
		Course course = em.find(Course.class, 10001L);
		logger.info("Courses: -> {}", course);
		logger.info("Student: -> {}", course.getStudents());
	}

}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;
import com.brains.jpa.hibernate.jpahibernatedemo.repository.StudentRepository;

@SpringBootApplication
public class JpaHibernateDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaHibernateDemoApplication.class, args);
	}

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private StudentRepository studentRepository;
	
	@Override
	public void run(String... args) throws Exception {
		studentRepository.insertHardcodedStudentCourse();
		Student student = new Student("Jack1");
		Course course = new Course("Microservices in 10 Steps");
		studentRepository.insertStudentCourse(student, course);
	}
}
				
			

Related Tutorials

@ManyToOne Mapping

OneToOne Unidirectional Student - Passport

  • @OneToMany – Lazy Fetching
  • @ManyToOne – Eager Fetching
  • @OneToOne – Eager Fetching
Annotations ending with ToOne are always Eager Fetching.
				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			
				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			

Many reviews for 1 Course, here Course 10001 has 5 reviews.


				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String name;
	
	@Setter(AccessLevel.NONE)
	@OneToMany(mappedBy = "course")
	private List<Review> reviews = new ArrayList<>();
	
	public void addReview(Review review) {
		this.reviews.add(review);
	}
	
	public void removeReview(Review review) {
		this.reviews.remove(review);
	}
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Review {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	private String rating;
	
	@NonNull
	private String description;
	
	@ToString.Exclude
	@ManyToOne
	private Course course;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
	
	@ToString.Exclude
	@OneToOne(fetch = FetchType.LAZY, mappedBy = "passport")
	private Student student;
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
}
				
			
				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'G12345');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description, course_id)
values(50001,'1', 'Great course', 10001);
insert into review(id, rating, description, course_id)
values(50002,'3', 'Nice course', 10001);
insert into review(id, rating, description, course_id)
values(50004,'5', 'Good you are writing', 10003);
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Review;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class CourseRepository {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	EntityManager em;
	
	public Course findById(Long id) {
		return em.find(Course.class, id);
	}
	
	public void deleteById(Long id) {
		Course course = findById(id);
		em.remove(course);
	}
	
	public Course save(Course course) {
		if(course.getId() == null) {
			//insert
			em.persist(course);
		}else {
			//update
			em.merge(course);
		}
		return course;
	}
	
	public void playWithEntityManager() {
		Course course1 = new Course("AngularJs in 100 Steps");
		em.persist(course1);
		
		Course course2 = findById(10001L);
		course2.setName("JPA in 5 steps -- updated");
	}

	public void addHardcodedReviewsForCourse() {
		Course course = findById(10003L);
		logger.info("course Reviews -> {}", course.getReviews());
		Review review1 = new Review("3", "Better");
		Review review2 = new Review("5", "Superb");
		
		course.getReviews().add(review1);
		review1.setCourse(course);
		
		course.getReviews().add(review2);
		review2.setCourse(course);

		em.persist(review1);
		em.persist(review2);
		logger.info("course Reviews -> {}", course.getReviews());
	}
	
	public void addDynamicReviewsForCourse(Long courseId, List<Review> reviews) {
		Course course = findById(courseId);
		logger.info("course Reviews -> {}", course.getReviews());
		
		for(Review review : reviews) {
			course.getReviews().add(review);
			review.setCourse(course);
			em.persist(review);
		}
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.JpaHibernateDemoApplication;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Review;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class CourseRepositoryTest {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	CourseRepository courseRepository;
	
	@Autowired
	EntityManager em;
	
	@Test
	@Transactional
	void retrieveReviewsForCourse() {
		Course course = courseRepository.findById(10001L);
		logger.info("Reviews: -> {}", course.getReviews());
	}
	
	@Test
	@Transactional
	void retrieveCourseForReview() {
		Review review = em.find(Review.class, 50001L);
		logger.info("Reviews: -> {}", review);
		Course course = review.getCourse();
		logger.info("Course: -> {}", course);
	}

}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Review;
import com.brains.jpa.hibernate.jpahibernatedemo.repository.CourseRepository;

@SpringBootApplication
public class JpaHibernateDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaHibernateDemoApplication.class, args);
	}

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private CourseRepository courseRepository;
	
	@Override
	public void run(String... args) throws Exception {
		List<Review> reviews = new ArrayList<>();
		reviews.add(new Review("3", "Better"));
		reviews.add(new Review("5", "Superb"));
		reviews.add(new Review("4", "Wow"));
		courseRepository.addDynamicReviewsForCourse(10001L, reviews);
	}
}
				
			
				
					Hibernate: 
    select
        c1_0.id,
        c1_0.created_date,
        c1_0.last_updated_date,
        c1_0.name 
    from
        course c1_0 
    where
        c1_0.id=?
Hibernate: 
    select
        r1_0.course_id,
        r1_0.id,
        r1_0.description,
        r1_0.rating 
    from
        review r1_0 
    where
        r1_0.course_id=?
2023-02-25T22:10:56.790+05:30  INFO 69975 --- [           main] c.b.j.h.j.repository.CourseRepository    : course Reviews -> [Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)]
Hibernate: 
    select
        next value for review_seq
Hibernate: 
    select
        next value for review_seq
Hibernate: 
    insert 
    into
        review
        (course_id, description, rating, id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        review
        (course_id, description, rating, id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        review
        (course_id, description, rating, id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    select
        c1_0.id,
        c1_0.created_date,
        c1_0.last_updated_date,
        c1_0.name 
    from
        course c1_0 
    where
        c1_0.id=?
Hibernate: 
    select
        r1_0.course_id,
        r1_0.id,
        r1_0.description,
        r1_0.rating 
    from
        review r1_0 
    where
        r1_0.course_id=?
2023-02-25T22:10:56.984+05:30  INFO 69975 --- [           main] c.b.j.h.j.r.CourseRepositoryTest         : Reviews: -> [Review(id=1, rating=3, description=Better), Review(id=2, rating=5, description=Superb), Review(id=3, rating=4, description=Wow), Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)]
Hibernate: 
    select
        r1_0.id,
        c1_0.id,
        c1_0.created_date,
        c1_0.last_updated_date,
        c1_0.name,
        r1_0.description,
        r1_0.rating 
    from
        review r1_0 
    left join
        course c1_0 
            on c1_0.id=r1_0.course_id 
    where
        r1_0.id=?
2023-02-25T22:10:57.005+05:30  INFO 69975 --- [           main] c.b.j.h.j.r.CourseRepositoryTest         : Reviews: -> Review(id=50001, rating=1, description=Great course)
Hibernate: 
    select
        r1_0.course_id,
        r1_0.id,
        r1_0.description,
        r1_0.rating 
    from
        review r1_0 
    where
        r1_0.course_id=?
2023-02-25T22:10:57.005+05:30  INFO 69975 --- [           main] c.b.j.h.j.r.CourseRepositoryTest         : Course: -> Course(id=10001, name=JPA in 5 steps, reviews=[Review(id=1, rating=3, description=Better), Review(id=2, rating=5, description=Superb), Review(id=3, rating=4, description=Wow), Review(id=50001, rating=1, description=Great course), Review(id=50002, rating=3, description=Nice course)], lastUpdatedDate=2023-02-25T22:10:56.658209, createdDate=2023-02-25T22:10:56.658209)

				
			

Related Tutorials

@OneToOne Mapping

OneToOne Unidirectional Student - Passport

@OneToOne mapping is by default Eager Fetching means it would automatically call the record of the rows of other table which is mapped.

Here Student class owns the mapping of the Passport and hence @OneToOne mapping is defined in the Student class.

@OneToOne(fetch = FetchType.LAZY)

Lazy will call the database only when it is required like student.getPassport();

If LAZY fetch is defined, we must use @Transactional with the method.

If the relationship is bidirectional, the non-owning side must use the mappedBy element of the OneToOne annotation to specify the relationship field or property of the owning side.

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			
				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import java.time.LocalDateTime;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String name;
	
	@UpdateTimestamp
	private LocalDateTime lastUpdatedDate;

	@CreationTimestamp
	private LocalDateTime createdDate;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Review {

	@Id
	@GeneratedValue
	private Long id;
	
	private String rating;
	
	private String description;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	//@OneToOne
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
}

				
			
				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);
insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'G12345');

insert into student(id, name, passport_id)
values(20001,'Ranga',40001);
insert into student(id, name, passport_id)
values(20002,'Adam',40002);
insert into student(id, name, passport_id)
values(20003,'Jane',40003);

insert into review(id, rating, description)
values(50001,'1', 'Great course');
insert into review(id, rating, description)
values(50002,'3', 'Nice course');
insert into review(id, rating, description)
values(50004,'5', 'Good you are writing');
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.repository;

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

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Passport;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@Repository
@Transactional
public class StudentRepository {

	@Autowired
	EntityManager em;
	
	public Student findById(Long id) {
		return em.find(Student.class, id);
	}
	
	public void saveStudentWithPassport() {
		Passport passport = new Passport("Z12345");
		em.persist(passport);
		Student student1 = new Student("Mike");
		student1.setPassport(passport);
		em.persist(student1);
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Passport;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;
import com.brains.jpa.hibernate.jpahibernatedemo.repository.StudentRepository;

import jakarta.persistence.EntityManager;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class StudentRepositoryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	StudentRepository studentRepository;
	
	@Autowired
	EntityManager em;
	
	// Use Transactional when Lazy fetch is defined
	@Transactional
	@Test
	void retrieveStudentAndPassport() {
		Student student = em.find(Student.class, 20001L);
		logger.info("Student ->  {}", student);
		Passport passport = student.getPassport();
		logger.info("Passport ->  {}", passport);
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.brains.jpa.hibernate.jpahibernatedemo.repository.CourseRepository;
import com.brains.jpa.hibernate.jpahibernatedemo.repository.StudentRepository;

@SpringBootApplication
public class JpaHibernateDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaHibernateDemoApplication.class, args);
	}

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private CourseRepository courseRepository;
	
	@Autowired
	private StudentRepository studentRepository;
	
	@Override
	public void run(String... args) throws Exception {
		studentRepository.saveStudentWithPassport();
	}
}
				
			

OneToOne Bidirectional Student - Passport

In Birectional mapping, mappedBy is added on the non-owning side(Passport) and mapped with the owning side(Student).

mappedBy ensures that the student_id column is not created in the Passport class.

 

				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String number;
	
	@ToString.Exclude
	@OneToOne(fetch = FetchType.LAZY, mappedBy = "passport")
	private Student student;
}
				
			
@ToString.Exclude is added in the Passport class because OneToOne mapping is available in both the Student and Passport classes, and to avoid the duplication of toString methods, one of the class need to include ToString.Exclude.
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@RequiredArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@NonNull
	@Column(nullable = false)
	private String name;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Passport passport;
}
				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Passport;
import com.brains.jpa.hibernate.jpahibernatedemo.entity.Student;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class StudentRepositoryTest {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	EntityManager em;
	
	@Test
	@Transactional
	void retrievePassportAndStudent() {
		Passport passport = em.find(Passport.class, 40001L);
		logger.info("Passport ->  {}", passport);
		Student student = passport.getStudent();
		logger.info("Student ->  {}", student);
	}
}

				
			
				
					2023-02-25T10:48:27.022+05:30  INFO 5022 --- [           main] c.b.j.h.j.StudentRepositoryTest          : Passport ->  Passport(id=40001, number=E12345, student=Student(id=20001, name=Ranga))
2023-02-25T10:48:27.022+05:30  INFO 5022 --- [           main] c.b.j.h.j.StudentRepositoryTest          : Student ->  Student(id=20001, name=Ranga)

				
			

Related Tutorials

Entities Relationships

With NamedQuery, we can reuse the query in multiple locations.

				
					<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.brains.jpa.hibernate</groupId>
	<artifactId>jpa-hibernate-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa-hibernate-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

				
			
				
					spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=FALSE
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true

# Turn Statistics ON
#spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
#logging.level.root=trace
# Show all queries
spring.jpa.show-sql=true

# Format the queries
spring.jpa.properties.hibernate.format_sql=true
				
			

For writing multiple NamedQuery, we need to use the @NamedQueries

				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Course {
	
	@Id
	@GeneratedValue
	private Long id;
	
	private String name;
	
	public Course(String name) {
		this.name = name;
	}
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Passport {

	@Id
	@GeneratedValue
	private Long id;
	
	@Column(nullable = false)
	private String number;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Review {

	@Id
	@GeneratedValue
	private Long id;
	
	private String rating;
	
	private String description;
}

				
			
				
					package com.brains.jpa.hibernate.jpahibernatedemo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Student {

	@Id
	@GeneratedValue
	private Long id;
	
	@Column(nullable = false)
	private String name;
}

				
			
				
					insert into course(id, name, created_date, last_updated_date) 
values(10001, 'JPA in 5 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into course(id, name, created_date, last_updated_date) 
values(10002, 'JDBC in 10 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into course(id, name, created_date, last_updated_date) 
values(10003, 'JPQL in 50 steps', LOCALTIMESTAMP, LOCALTIMESTAMP);

insert into student(id, name)
values(20001,'Ranga');
insert into student(id, name)
values(20002,'Adam');
insert into student(id, name)
values(20003,'Jane');

insert into passport(id, number)
values(40001,'E12345');
insert into passport(id, number)
values(40002,'F212345');
insert into passport(id, number)
values(40003,'G12345');

insert into review(id, rating, description)
values(50001,'1', 'Great course');
insert into review(id, rating, description)
values(50002,'3', 'Nice course');
insert into review(id, rating, description)
values(50004,'5', 'Good you are writing');
				
			

Use NativeQueries when multiple rows are required to be updated/inserted. Or in case there the JPA is not supported and need to use the database functionality directly.

				
					package com.brains.jpa.hibernate.jpahibernatedemo;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.brains.jpa.hibernate.jpahibernatedemo.entity.Course;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

@SpringBootTest(classes = JpaHibernateDemoApplication.class)
class JPQLTest {

	@Autowired
	EntityManager em;
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
//	@Test
	void jpql_basic() {
		Query query = em.createNamedQuery("query_get_all_courses");
		List resultList = query.getResultList();
		logger.info("list - jpql_basic: -> {}", resultList);
	}
	
//	@Test
	void jpql_typed() {
		TypedQuery<Course> query = em.createNamedQuery("query_get_all_courses", Course.class);
		List resultList = query.getResultList();
		logger.info("Course - jpql_typed: -> {}", resultList);
	}

//	@Test
	void jpql_where() {
		TypedQuery<Course> query = em.createNamedQuery("query_get_10_courses", Course.class);
		List resultList = query.getResultList();
		logger.info("Named Course: -> {}", resultList);
	}
	
	//@Test
	void native_queries_basic() {
		Query query = em.createNativeQuery("select * from course", Course.class);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
	//@Test
	void native_queries_with_param() {
		Query query = em.createNativeQuery("select * from course where id = ?", Course.class);
		query.setParameter(1, 10001L);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
//	@Test
	void native_queries_with_named_param() {
		Query query = em.createNativeQuery("select * from course where id = :id", Course.class);
		query.setParameter("id", 10002L);
		List resultList = query.getResultList();
		logger.info("list - native_basic: -> {}", resultList);
	}
	
	@Test
	@Transactional
	void native_queries_to_update() {
		Query query = em.createNativeQuery("update course set last_updated_date = LOCALTIMESTAMP", Course.class);
		int numOfRowsUpdated = query.executeUpdate();
		logger.info("numOfRowsUpdated: -> {}", numOfRowsUpdated);
	}
}

				
			

Related Tutorials