09 Spring Boot 3 Jpa Advanced Mappings
09 Spring Boot 3 Jpa Advanced Mappings
Advanced Mappings
© luv2code LLC
Basic Mapping
Hibernate
• Multiple Tables
• One-to-Many, Many-to-One
• Many-to-Many
Instructor
Instructor
Detail
Course
Course
Instructor
Course
Course
Course Student
Course Student
Course Student
Course Student
• Cascade
• Foreign key:
Table: instructor
Table: instructor_detail
Instructor
Instructor
Detail
save
Instructor
Instructor
Detail
delete
Table: instructor
Table: instructor_detail
Course Student
Course Student
Course Student
Course Student
Course Student
Course Student
Course Student
Course Student
Course
Course
Instructor
Course
Course
Instructor
Instructor
Detail
Instructor
Instructor
Detail
© luv2code LLC
One-to-One Mapping
• An instructor can have an “instructor detail” entity
Instructor
Instructor
Detail
Instructor
Instructor
Detail
);
...
...
Table: instructor
Table: instructor_detail
…
CREATE TABLE `instructor` (
…
);
• Referential Integrity
• Ensures only valid data is inserted into the foreign key column
www.luv2code.com
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
www.luv2code.com
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
www.luv2code.com
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="youtube_channel")
private String youtubeChannel;
www.luv2code.com
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="youtube_channel")
private String youtubeChannel;
@Column(name="hobby")
private String hobby;
www.luv2code.com
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="youtube_channel")
private String youtubeChannel;
@Column(name="hobby")
private String hobby;
// constructors
// getters / setters
}
www.luv2code.com
Step 3: Create Instructor class
@Entity
@Table(name="instructor")
public class Instructor {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
@Column(name="email")
private String email;
…
// constructors, getters / setters
}
www.luv2code.com
Step 3: Create Instructor class - @OneToOne
@Entity
@Table(name="instructor")
public class Instructor {
…
@OneToOne
@JoinColumn(name=“instructor_detail_id")
private InstructorDetail instructorDetail;
…
// constructors, getters / setters
}
www.luv2code.com
Entity Lifecycle
Operations Description
Refresh Reload / synch object with data from db. Prevents stale data
www.luv2code.com
Entity Lifecycle - session method calls
New / Transient
commit
save / persist rollback / new
delete / remove
Persistent /
refresh Removed
Managed
persist/rollback
Detached
www.luv2code.com
Cascade
• Recall: You can cascade operations
Instructor
Instructor
Detail
save
www.luv2code.com
Cascade Delete Foreign key
column
Table: instructor
Table: instructor_detail
www.luv2code.com
@OneToOne - Cascade Types
www.luv2code.com
Configure Cascade Type
@Entity
@Table(name="instructor")
public class Instructor {
…
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="instructor_detail_id")
private InstructorDetail instructorDetail;
…
// constructors, getters / setters
} By default, no operations are cascaded.
www.luv2code.com
Configure Multiple Cascade Types
@OneToOne(cascade={CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.PERSIST,
CascadeType.REFRESH,
CascadeType.REMOVE})
www.luv2code.com
Step 4 - Creating Spring Boot - Command Line App
• We will create a Spring Boot - Command Line App
MainApp AppDAO
Instructor
Instructor
Detail
@Repository
public class AppDAOImpl implements AppDAO {
@Override
@Transactional
public void save(Instructor theInstructor) {
entityManager.persist(theInstructor);
Save the
} Java object
}
@Repository
public class AppDAOImpl implements AppDAO {
@Bean
public CommandLineRunner commandLineRunner(AppDAO appDAO) {
return runner -> { private void createInstructor(AppDAO appDAO) {
System.out.println("Done!");
In AppDAO, delegated to
}
}
entityManager.persist(…)
© luv2code LLC
Define DAO implementation
@Repository
public class AppDAOImpl implements AppDAO { We’ll add supporting code in the video:
interface, main app
…
@Override
public Instructor findInstructorById(int theId) {
return entityManager.find(Instructor.class, theId);
}
}
This will ALSO retrieve the instructor details object
© luv2code LLC
Define DAO implementation
@Repository
public class AppDAOImpl implements AppDAO {
…
We’ll add supporting code in the video:
@Override interface, main app
@Transactional
public void deleteInstructorById(int theId) {
Because of CascadeType.ALL
© luv2code LLC
One-to-One Mapping
• We currently have a uni-directional mapping
Instructor
Instructor
Detail
www.luv2code.com
New Use Case
• If we load an InstructorDetail
Instructor
Instructor
Detail
www.luv2code.com
Bi-Directional
• Bi-Directional relationship is the solution
Instructor
Instructor
Detail
www.luv2code.com
Bi-Directional - The Good News
www.luv2code.com
Development Process: One-to-One (Bi-Directional)
Step-
By-S
1. Make updates to InstructorDetail class: tep
www.luv2code.com
Step 1.1: Add new field to reference Instructor
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
…
www.luv2code.com
Step 1.2: Add getter/setter methods Instructor
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
…
www.luv2code.com
Step 1.3: Add @OneToOne annotation
@Entity
@Table(name="instructor_detail")
public class InstructorDetail { Refers to “instructorDetail” property
… in “Instructor” class
@OneToOne(mappedBy="instructorDetail")
private Instructor instructor;
www.luv2code.com
More on mappedBy public class InstructorDetail {
…
@OneToOne(mappedBy="instructorDetail")
private Instructor instructor;
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="instructor_detail_id")
private InstructorDetail instructorDetail;
www.luv2code.com
Add support for Cascading Cascade all operations
@Entity to the associated Instructor
@Table(name="instructor_detail")
public class InstructorDetail {
…
@OneToOne(mappedBy="instructorDetail", cascade=CascadeType.ALL)
private Instructor instructor;
www.luv2code.com
Define DAO interface
import com.luv2code.cruddemo.entity.Instructor;
@Repository
public class AppDAOImpl implements AppDAO {
Because
// inject entity manager using constructor injection of default behavior of @OneToOne
…
@Override
public InstructorDetail findInstructorDetailById(int theId) {
@Bean
public CommandLineRunner commandLineRunner(AppDAO appDAO) {
return runner -> {
findInstructorDetail(appDAO);
} private void findInstructorDetail(AppDAO appDAO) {
… int theId = 1;
System.out.println("Finding instructor detail id: " + theId);
}
}
© luv2code LLC
One-to-Many Mapping
• An instructor can have many courses
• Bi-directional
Course
Course
Instructor
Course
Course
Course
Course
Instructor
Course
Course
Course
Course
Instructor
Course
Course
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="title")
private String title;
…
// constructors, getters / setters
}
@Entity
@Table(name="course")
public class Course {
…
@ManyToOne
@JoinColumn(name="instructor_id")
private Instructor instructor;
…
// constructors, getters / setters
}
@OneToMany(mappedBy="instructor")
private List<Course> courses;
@ManyToOne
@JoinColumn(name="instructor_id")
private Instructor instructor;
@OneToMany(mappedBy="instructor",
cascade={CascadeType.PERSIST, CascadeType.MERGE
CascadeType.DETACH, CascadeType.REFRESH})
private List<Course> courses;
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE
CascadeType.DETACH, CascadeType.REFRESH})
@JoinColumn(name="instructor_id")
private Instructor instructor;
…
// constructors, getters / setters
}
Do not apply
cascading deletes!
if (courses == null) {
courses = new ArrayList<>();
}
courses.add(tempCourse);
tempCourse.setInstructor(this);
}
…
}
© luv2code LLC
Fetch Types: Eager vs Lazy Loading
• When we fetch / retrieve data, should we retrieve EVERYTHING?
Course
Course
Instructor
Course
Course
Course
Course
Instructor
Course
Course
Course
• Eager loading would still load all students for each course …. not good!
List of
courses
@Entity
@Table(name="instructor")
public class Instructor {
…
@OneToMany(fetch=FetchType.LAZY, mappedBy=“instructor”)
private List<Course> courses;
@OneToOne FetchType.EAGER
@OneToMany FetchType.LAZY
@ManyToOne FetchType.EAGER
@ManyToMany FetchType.LAZY
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="instructor_id")
private Instructor instructor;
© luv2code LLC
Previous Solution: Eager
• Eager will retrieve everything … all of the courses for an instructor
Course
Course
Instructor
Course
Course
@Entity
@Table(name="instructor")
public class Instructor {
…
@OneToMany(fetch=FetchType.LAZY, mappedBy=“instructor”)
private List<Course> courses;
…
FetchType for @OneToMany defaults to lazy …
} But I will explicitly list it for readability
@Override
public List<Course> findCoursesByInstructorId(int theId) {
// create query
TypedQuery<Course> query = entityManager.createQuery("from Course where instructor.id = :data", Course.class);
query.setParameter("data", theId);
// execute query
List<Course> courses = query.getResultList(); Since fetch type for courses is lazy
return courses; File: CruddemoApplication.java
This will retrieve the instructor
}
private void findCoursesForInstructor(AppDAO appDAO) { WITHOUT courses
int theId = 1;
© luv2code LLC
Previous Solution: Find Courses for Instructor
• Previous solution was OK … but …
• Also keep the LAZY option available … don’t change fetch type
query.setParameter("data", theId);
File: CruddemoApplication.java
// execute query
Instructor instructor = query.getSingleResult(); private void findInstructorWithCoursesJoinFetch(AppDAO appDAO) {
return instructor; int theId = 1;
}
// find the instructor
System.out.println("Finding instructor id: " + theId);
Instructor tempInstructor = appDAO.findInstructorByIdJoinFetch(theId);
This code will still retrieve Instructor AND Courses
System.out.println("tempInstructor: " + tempInstructor);
System.out.println("the associated courses: " + tempInstructor.getCourses());
System.out.println("Done!");
}
• appDAO.findInstructorById(…)
• appDAO.findInstructorByIdJoinFetch(…)
© luv2code LLC
Update Instructor
• Find an instructor by ID
@Override
@Transactional
public void update(Instructor tempInstructor) {
entityManager.merge(tempInstructor);
}
int theId = 1;
System.out.println("Done");
} Call DAO method
to update database
© luv2code LLC
Update Course
• Find a course by ID
@Override
@Transactional
public void update(Course tempCourse) {
entityManager.merge(tempCourse);
}
appDAO.update(tempCourse);
System.out.println("Done");
Call DAO method
}
to update database
© luv2code LLC
Delete instructor
• Find an instructor by ID
@Override
@Transactional
public void deleteInstructorById(int theId) {
}
entityManager.remove(tempInstructor);
We only delete the instructor …
not the associated course
based on our cascade types
(`hb-03-one-to-many`.`course`,
CONSTRAINT `FK_INSTRUCTOR` FOREIGN KEY (`instructor_id`) REFERENCES `instructor` (`id`))
int theId = 1;
System.out.println("Deleting instructor id: " + theId);
appDAO.deleteInstructorById(theId);
System.out.println("Done!");
}
© luv2code LLC
Delete course
• Delete the course by ID
@Override
@Transactional
public void deleteCourseById(int theId) {
appDAO.deleteCourseById(theId);
System.out.println("Done!");
© luv2code LLC
One-to-Many Mapping
• A course can have many reviews
• Uni-directional
Review
Review
Course
Review
Review
Review
Review
Course
Review
Review
@OneToMany (uni)
@OneToOne
This section
is new!
@OneToMany (bi)
@OneToMany (uni)
@ManyToOne
...
);
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="comment")
private String comment;
…
// constructors, getters / setters
}
@Entity
@Table(name="course")
public class Course {
…
// getter / setters
…
@Entity
@Table(name="course") Refers to “course_id” column
public class Course {
in “review” table
…
@OneToMany
@JoinColumn(name="course_id")
private List<Review> reviews;
// getter / setters
…
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="course_id")
private List<Review> reviews;
@OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="course_id")
private List<Review> reviews;
if (reviews == null) {
reviews = new ArrayList<>();
}
reviews.add(tempReview);
}
…
}
© luv2code LLC
Many-to-Many Mapping
• A course can have many students
Course Student
Course Student
Course Student
Course Student
Course Student
Course Student
Join
Course Table Student
Course Student
Join Table
- Pacman
Join Table
- Pacman
- Rubik’s Cube
- Atari 2600
...
);
CONSTRAINT `FK_STUDENT`
FOREIGN KEY (`student_id`)
REFERENCES `student` (`id`)
…
);
Table Column
CONSTRAINT `FK_STUDENT`
Table
FOREIGN KEY (`student_id`)
Column
REFERENCES `student` (`id`)
…
);
// getter / setters
…
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="course_id"),
inverseJoinColumns=@JoinColumn(name="student_id")
)
private List<Student> students;
// getter / setters
…
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="course_id"),
inverseJoinColumns=@JoinColumn(name="student_id")
)
private List<Student> students;
// getter / setters
…
• For other side (inverse), look at the student_id column in the course_student table
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="course_id"),
inverseJoinColumns=@JoinColumn(name="student_id")
)
private List<Student> students;
// getter / setters
…
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="student_id"),
inverseJoinColumns=@JoinColumn(name="course_id")
)
private List<Course> courses;
// getter / setters
…
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="student_id"),
inverseJoinColumns=@JoinColumn(name="course_id")
)
private List<Course> courses;
// getter / setters
…
• For other side (inverse), look at the course_id column in the course_student table
@ManyToMany
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="student_id"),
inverseJoinColumns=@JoinColumn(name="course_id")
)
private List<Course> courses;
Inverse /
other side We are here
Course Student
Course Student
Course Student
Course Student