February 24th, 2023
@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

Leave a Reply

Your email address will not be published. Required fields are marked *