Hibernate Advanced Mappings

Last Updated on: February 8, 2021 pm

Hibernate Advanced Mappings

@OneToOne

Entity Lifecycle

  • Detach - not associated with a Hibernate session
  • Merge
  • Persist
  • Refresh - sync object with db

Entity Lifecycle

Cascade Type

  • PERSIST - Related entity will also be persisted
  • REMOVE -
  • REFRESH
  • DETACH
  • REMOVE
  • ALL
1
@OneToOne(cascade=CascadeType.ALL)

Note: By default, no operations are cascaded.

Configure Multiple Cascade Types

1
2
@OneToOne(cascade={CascadeType.DETACH,
CascadeType.MERGE})

Uni-Directional Example

1
2
3
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "instructor_detail_id")
private InstructorDetail instructorDetail;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void main(String[] args) {
// Create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Instructor.class)
.addAnnotatedClass(InstructorDetail.class)
.buildSessionFactory();

Session session = factory.getCurrentSession();
try{
// create
Instructor instructor = new Instructor("Dylan", "Zhang", "dylan@test.com");
InstructorDetail instructorDetail = new InstructorDetail("http://github.com", "Wine");

// associate
instructor.setInstructorDetail(instructorDetail);

//start
session.beginTransaction();

// This will ALSO save the detail object
// because of the CascadeType.ALL
System.out.println("Saving instructor: "+instructor);
session.save(instructor);

// commit transaction
session.getTransaction().commit();

System.out.println("Done!");
} finally {
factory.close();
}
}
}

Bi-Directional Example

If we load an InstructorDetail, we want to get the associated Instructor.

Process

  1. Make updates to InstructorDetail class.

    1. Add new field to reference Instructor.
    2. Add getter and setter method.
    3. Add @OneToOne annotation.
  2. Create Main App.

1
2
@OneToOne(mappedBy="instructorDetail", cascade=CascadeType.ALL)
private Instructor instructor;

MappedBy tells Hibernate:

  • Look at the instructorDetail property in the Instructor class.

    Use property name!

    1
    2
    3
    4
    5
    public class Instructor{
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="instructor_detail_id") // link to the db
    private InstructorDetail instructorDetail;
    }
  • Use the info from the Instructor class @JoinColumn

  • To help to find the associated instructor.

Exception Handling

1
2
3
4
5
6
7
try{
} catch (Exception e){
e.printStackTrace();
}finally {
session.close();
factory.close();
}

Delete Only InstructionDetail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int theId = 4;

// look for the associated
InstructorDetail instructorDetail = session.get(InstructorDetail.class, theId);

System.out.println("The Instructor Detail: " + instructorDetail);
System.out.println("The associated Instructor: " + instructorDetail.getInstructor());

// break teh bi-directional link
// remove the associated object reference
instructorDetail.getInstructor().setInstructorDetail(null);

// Delete the instructor detail and cascade delete the instructor
System.out.println("Deleting " + instructorDetail);
session.delete(instructorDetail);

// commit transaction
session.getTransaction().commit();
System.out.println("Done!");

Note: After selecting the instructor and instructorDetail, you should remove the association between those two and then perform deleting operation.

@OneToMany

Bi-Directional

Do not apply cascading deletes!

Course.class

1
2
3
@ManyToOne
@JoinColumn(name="instructor_id")
private Instructor instructor;

Instructor.class

1
2
3
@OneToMany(mappedBy="instructor") 
// refers to the "instructor" property in the "Course" class
private List<Course> courses;

Note: Apply ALL the cascading types except REMOVE!

Uni-Directional

Use Case

We have Course Entity and Review Entity. But a review without a course is meaningless, so if the course is deleted, then the related reviews should apply cascading deletes.

Example

1
2
3
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "course_id")
private List<Review> reviews;
1
2
3
4
5
6
7
8
9
public Review{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;

@Column(name = "comment")
private String comment;
}

@ManyToMany

@JoinTable

Mapping between two tables. Use the foreign keys to maintain relationship.

1
2
3
4
5
@JoinTable(
name="course_student",
joinColumns=@JoinColumn(name="course_id"),
inverseJoinColumn=@JoinColumn(name="student_id")
)
  1. Look at the course_id in the course_student table
  2. Inversely, look at the student_id column in the course_student table.

Note: Do not apply cascading deletes!

Eager vs Lazy Loading

Eager Loading

It will retrieve everything and load all entites.

1
2
3
@OneToMany(fetch = FetchType.EAGER,
mappedBy = "instructor") //refers the instructor property in the course.class
private List<Course> courses;

Lazy Loading

It will retrieve on request.

1
2
3
@OneToMany(fetch = FetchType.LAZY,
mappedBy = "instructor") //refers the instructor property in the course.class
private List<Course> courses;

Use Cases

Master View

we use the lazy loading.

Detail View

we retrieve the entity and necessary dependent entities.

Default Fetch Types

@OneToOne - EAGER

@OneToMany - LAZY

@ManyToOne - EAGER

@ManyToMany - LAZY

Resolve LAZY Loading Issue

Problem

If we close the session before the accessing LAZY data, what will we get?

1
2
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/hb-03-one-to-many?useSSL=false&serverTimezone=UTC]
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.luv2code.hibernate.demo.entity.Instructor.courses, could not initialize proxy - no Session

How can we access the LAZY data after the session is closed?

Solution1

1
System.out.println("luv2Code: The Courses: "+ instructor.getCourses());

Retrieve the LAZY data before the session is closed. And the LAZY data is stored in memory to access.

Solution2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// get the instructor from db
int theId = 1;

Query<Instructor> query =
session.createQuery("select i from Instructor i "
+ "JOIN FETCH i.courses "
+ "where i.id =:theInstructorId",
Instructor.class);

// set the parameter on query
query.setParameter("theInstructorId", theId);

// execute the query and get instructor
Instructor tempInstructor = query.getSingleResult();

Use FETCH JOIN to generate and execute HQL Query. Retrive the LAZY data.

Reference: Udemy, Spring & Hibernate for Beginners (including SpringBoot)