Hibernate with Kotlin

Hibernate is an ORM framework that seamlessly provides database connections through object mapping. It supports most of the modern relational databases and is considered a more sophisticated way compared to JDBC. When using along with Spring, it is best recommended to use Spring JPA's annotations instead of Hibernate's own. Spring JPA provides an abstraction that is further implemented by Hibernate. So basically you are using hibernate underneath JPA.

A quick overview of Hibernate key terminologies before moving forward:
  • SessionFactory: It is a heavy-weight object, only created once in application, reads the hibernate config file, creates session objects.
  • Session: Session is a wrapper around JDBC connection, main object to save/get objects, short-lived, use multiple sessions for operations, get session from sessionFactory.
  • Transaction: A Transaction represents a unit of work with the database and most of the RDBMS supports transaction functionality.
  • Query: Query objects use SQL or Hibernate Query Language (HQL) to retrieve data from the database and create objects.
  • Criteria: Criteria objects are used to create and execute object-oriented criteria queries to retrieve objects.
So how does the Hibernate development process really work? There are 3 simple steps to get started:
  1. Pre-Dev Config for database and Kotlin project
  2. Annotating Kotlin class (or good old XML configs)
  3. Writing code to perform database operations
Let's take a look at step 1 and what all we need to prepare before writing the actual code. 

I. Pre-Dev Config:

  • PostgreSQL table config: The first obvious step is to create tables, identify primary and foreign keys. I am using Postgres relational database and have already created instructor, instructor_detail, student, course, review, course_student tables.
  • Using the right JPA generation type strategy to manage primary keys: As a developer, nobody likes to manage primary keys. We just wish them to auto-populate every time we insert a new row. I am just going to use GenerationType.SEQUENCE strategy for all the entities which will use the default hibernate_sequence and share it across all the tables. We can always create separate sequences for each table and make hibernate use them.
  • Additional config for Kotlin - By default, the Kotlin data classes don’t have no parameter constructors. We can either write them or we can use a noarg plugin to generate them. (Refer: https://www.baeldung.com/kotlin-jpa)
  • Add a hibernate config file. Below is an example of my XML config file.
After all that DB and config setup, we are now ready to build a standalone hibernate project to make some CRUD operations. Step 2 and 3 are explained using multiple demo examples.

II. and III. Annotating Kotlin class & writing code to perform DB operations:

Basic CRUD Operations: Basic CRUD operations include create, read, update, and deleting data. I have created a Student class which has firstName, lastName, and email properties. The class is annotated with @Entity and @Table (table name from the Database) and its properties are annotated with @Column (column name from the database). It also has the id property mapped to the primary key column of the same name from the Database table. Following are a few examples of CRUD operations with Hibernate.

Advanced Mappings
One to One mapping: The Instructor class has one to one mapping with InstructorDetail. One Instructor can have only one InstructorDetail associated and vice a versa. Furthermore, the relationship between both of them can be unidirectional or bidirectional depending on the use case. If there's a unidirectional relationship defined in Instructor class, then we can fetch InstructorDetail from it but the vice versa is not possible. On the other hand, the bidirectional relationship makes it possible to fetch Instructor from InstructorDetail and vice a versa.
I would also like to mention the concept of cascading here. There are various cascading types available such as Persist, Remove, Refresh, Detach, Merge, and All. By looking at the options we can deduce that if any of these actions are performed on the calling object, the related object will undergo the same operation. For eg. we are defining CascadeType.ALL for InstructorDetail relationship defined on Instructor class. Let's say if we delete the Instructor, the InstructorDetail will also get deleted. In contrast, we are specifically leaving out CascateType.REMOVE for Instructor defined on InstructorDetail. That means if we delete InstructorDetail, Instructor will NOT get deleted. Note that by default no operations are cascaded.

One to Many mapping: The idea is one instructor can teach many courses. To make it work both ways, I have added a bidirectional relationship between Instructor and Course.

I have also created a Review class that follows the principle of one course having many reviews. Here's the thing, do we really want the Course-Review relationship a bidirectional? Logically speaking, we probably don't want to fetch course from review. So let's make it a unidirectional one.
Before moving on to the Many to Many mapping, I would like to take a pause and explain the concept of fetch type. There are 2 fetch types (loading strategies) available in hibernate, Eager loading, and Lazy loading. Eager loading will retrieve everything and lazy loading will retrieve on request. For eg. when you are using Eager loading and you load Instructor data, the hibernate will also load InstructorDetail, Course, and Review. This is not a good practice to follow in a production environment. You only need to load something that's needed to optimize your application and improve performance. That's where lazy loading comes into the picture. Lazy loading will only load Instructor data and retrieve everything else on demand. The only important thing to note here is lazy loading requires the session to be open otherwise you will encounter LazyLoadingException. The reason lazy loading should take place during the session's life cycle is that hibernate will make database calls using the session behind the scenes.

Many to Many mapping: One student can be enrolled in many courses and one course can have many students enrolled. This situation calls for Many to Many mapping and it needs a separate table in the database. I have established Many to Many relationship between Course and Student and also created a course_student table. Instead of @JoinColumn, we need to use @JoinTable annotation.

Before ending this blog here are a few important takeaways:

  • Rember to add Kotlin noarg plugin to avoid exceptions
  • Add a right JPA generation type strategy to manage primary keys
I hope you learned something new about using Hibernate with Kotlin in this blog post. The entire code can be found on my GitHub. Thanks for reading and stay tuned for more! Happy coding 😃

    Comments

    Popular Posts