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.
- Pre-Dev Config for database and Kotlin project
- Annotating Kotlin class (or good old XML configs)
- 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.
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
Post a Comment