//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.(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
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 list = query.getResultList();
logger.info("List size: -> {}", list.size());
for(Object[] result: list) {
logger.info("Course: {} Student: {}", result[0], result[1]);
}
}
4.0.0org.springframework.bootspring-boot-starter-parent3.0.2com.brains.jpa.hibernatejpa-hibernate-demo0.0.1-SNAPSHOTjpa-hibernate-demoDemo project for Spring Boot17org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-webcom.h2databaseh2runtimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
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 reviews = new ArrayList<>();
@ManyToMany(mappedBy = "courses")
@Setter(value = AccessLevel.NONE)
private List 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;
}
}
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 createQuery = em.createQuery("select c from Course c where c.students is empty", Course.class);
List 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 createQuery = em.createQuery("select c from Course c where size(c.students) > 2", Course.class);
List 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 createQuery = em.createQuery("select c from Course c order by size(c.students) desc", Course.class);
List 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 createQuery = em.createQuery("select s from Student s where s.passport.number like '%1234%'", Student.class);
List resultList = createQuery.getResultList();
logger.info("Courses: -> {}", resultList);
}
}
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
We use ManyToMany when Multiple relations are between tables.
4.0.0org.springframework.bootspring-boot-starter-parent3.0.2com.brains.jpa.hibernatejpa-hibernate-demo0.0.1-SNAPSHOTjpa-hibernate-demoDemo project for Spring Boot17org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-webcom.h2databaseh2runtimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
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.
Annotations ending with ToOne are always Eager Fetching.
4.0.0org.springframework.bootspring-boot-starter-parent3.0.2com.brains.jpa.hibernatejpa-hibernate-demo0.0.1-SNAPSHOTjpa-hibernate-demoDemo project for Spring Boot17org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-webcom.h2databaseh2runtimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
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.
@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.
4.0.0org.springframework.bootspring-boot-starter-parent3.0.2com.brains.jpa.hibernatejpa-hibernate-demo0.0.1-SNAPSHOTjpa-hibernate-demoDemo project for Spring Boot17org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-webcom.h2databaseh2runtimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
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
@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.
With NamedQuery, we can reuse the query in multiple locations.
4.0.0org.springframework.bootspring-boot-starter-parent3.0.2com.brains.jpa.hibernatejpa-hibernate-demo0.0.1-SNAPSHOTjpa-hibernate-demoDemo project for Spring Boot17org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-webcom.h2databaseh2runtimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
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;
}
}
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.