Course Content
Java Data Manipulation with Hibernate
Java Data Manipulation with Hibernate
Entity Relations and Cascade Types
Entity Relationships
As you may know from the Relational Databases and Normalization course, in SQL, there are certain relationships between tables. Similarly, in Hibernate, one entity can reference another and vice versa. These are called Entity Relationships. There are a total of 4 types of such relationships.
One-to-One
This type of relationship indicates that one entity is associated with only one other entity. For example, a country may have only one capital. If we represent this in terms of tables and entities, there will be a One-to-One relationship between them. In Hibernate, to specify such a relationship, we use the @OneToOne
annotation:
If this annotation is present above an entity field, then in the corresponding table, a foreign key column will be created, which will contain a reference to the identifier of the associated entity.
It will look like this:
Obviously, in the "cities" table, capitals will be stored with their IDs. For example, under ID 4, there will be "Managua," under 11, "Washington," and so on.
One-to-Many
In such a relationship, one entity will be associated with multiple other entities. It's easy to understand with the example of a director and their movies. One director can have many different movies, but each movie should have only one director.
Note
Yes, I know that there are cases where multiple directors work on a movie, but for our example now, that's not important.
To denote such a relationship, the @OneToMany
annotation is placed above the field in the entity class. The field must be a list:
If such an annotation is present above the field, then a separate table will be created in the database, which will contain the identifiers of this entity and the associated entity.
It will look like this:
Such IDs will reference both entities, showing the relationships between them.
Many-to-One
In this type, many entities are related to one. You might think of it as the reverse of One-to-Many, and you would be correct. For a better understanding, consider the example of student-university relationships. Many students can be associated with one university.
To establish such a relationship, you need to specify the @ManyToOne
annotation above the field that is not a collection:
If this annotation is present above the entity field, the corresponding table will receive a foreign key column containing a reference to the identifier of the associated entity.
It will look like this:
This resembles One-to-One, but here, different students may have the same reference to the university ID.
Many-to-Many
In this type of relationship, many entities are associated with many entities. You can think of this relationship in terms of a driver and a car. One driver can have many cars, and one car can have many drivers (for example, in a taxi service).
In the code, you need to place the @ManyToMany
annotation above the field in the entity:
If this annotation is present above a field of the entity (which must be a collection), then a separate table will be created in the database, which contains identifiers of the current entity and the associated entity.
It will look like this:
As you can see, here, a driver can have multiple cars, and likewise, a car can have multiple drivers.
You may have noticed that in some cases, we only need to specify the annotation in one class, while in other cases, it is necessary to specify it in both entity classes simultaneously. Let's discuss this in more detail.
Unidirectional and Bidirectional Relationships
Unidirectional and bidirectional relationships between entities indicate how they interact.
- Unidirectional Relationships: These are the relationships we have discussed before. In such a relationship, the relationship annotation is specified only in one entity class. Simply put, the class that contains the annotated field knows that it is related to another class, while the other class, which does not contain the annotation, is unaware of the relationship. It's not complicated, but we're more interested in the second case;
- Bidirectional Relationships: In this type of relationship, Class
A
is aware of its relationship with ClassB
, just as ClassB
is aware of its relationship with ClassA
. The relationship annotation will be specified in both entity classes, and it will look like this:
A
B
@Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String name; @OneToMany private List<B> list; }
Interaction between these two entities occurs in both directions.
To ensure consistency during data operations, we must define the "owning side" and the "inverse side." The owning side is the side that controls the relationship, and updates to the relationship in the database occur only after changes are made on the owning side.
To determine the owning side, we use a parameter in the relationship annotation. The mappedBy
parameter uses the name of the field in the entity we want to make the owning side. In other words, we use this annotation in the entity we want to make the inverse side.
For a clearer example, let's make class B
the owning side:
Now entity B
has become the owning side in these bidirectional relationships. The data will be written to the database only when changes are made in class B
.
This is done to avoid creating a new table containing the identifiers of these two entities. Now, instead, a separate column, "A_id
" will be added to the entity B
table, where the relationship IDs will be specified.
It is considered good practice to make the "ManyToOne" side the owning side. For bidirectional relationships "Many-to-Many," either side can be designated as the owning side.
Later, we'll use this in practice, and you'll better understand how it works and why it's necessary.
Fetch Types
In entity relationships, different fetch types are used. There are two of them, let's discuss each:
- EAGER means that all necessary data is fetched in a single query. When we retrieve an entity from the database that has
@OneToOne
or@ManyToOne
fields, we also get complete information about these related entities. This means that with one query to the database, we immediately retrieve information from two tables, which consumes resources, memory, and execution time. Such a fetch type significantly impacts optimization; - LAZY means that data is fetched from the database only when it is actually needed for a specific operation. When we retrieve an entity from the database that has
@OneToMany
or@ManyToMany
fields, we do not get information about these related entities. This is a significant advantage because we do not fetch unnecessary information from the database, only the data we need. This means that only the table we are referring to is affected, without touching the tables associated with this entity.
Note
Remembering this is quite simple: Relationships ending in
Many
will have a fetch type of LAZY; the rest will be EAGER.
It is recommended to always use LAZY fetch type, as it makes the code more optimized. You can do this using the command fetch = FetchType.LAZY
in the annotation parameters.
Here's how it looks in the code:
Now, when executing a query for this entity, we will only affect it, and only access other data if necessary.
Cascade Types
Now that we've learned how to interact with only the entity we're working with, let's learn how to specify specific fields on which interaction should propagate when relating entities in any case. Cascades will help us with this.
Note
Such cascades are part of the JPA ( Jakarta Persistence API ) library, which is the parent class of Hibernate. So in Hibernate, we can also use such classes.
There are a total of 4 types:
PERSIST
— invoking thepersist()
method is propagated to related entities;MERGE
— invoking themerge()
method is propagated to related entities;REMOVE
— invoking theremove()
method is propagated to related entities;ALL
- all the above-mentioned operations are propagated to related entities.
Each of these cascade types serves its purpose.
Let's imagine a situation where we are saving a new student, and we want changes to also be made to the "universities" table, for example, increasing the number of students.
Here's how we can implement this:
In this case, when making changes using the persist()
method on the Student Entity, changes will also be made to the University Entity.
Assigning a Department to an Employee
For now, the scary theory in this chapter is finished.
Now, let's recall the employee management project we've been working on and think about the relationship between the Employee
and Department
entities.
It's obvious that an employee can only work in one department, while many employees can work in one department. In this case, we can conclude that a One-to-Many relationship will be established from the side of the Employee
and a Many-to-One relationship from the side of the Department
.
Let's establish a unidirectional relationship in these tables.
To achieve this, we'll add the annotation @ManyToOne(fetch = FetchType.LAZY)
, and also use the new annotation JoinColumn(name = "department_id")
, to avoid creating a new table.
Yes, this approach also has its place, as bidirectional relationships are quite complex to understand and implement, and this way we'll get the benefits of bidirectional relationships without unnecessary hassle.
The Employee
entity will look like this:
No other changes are needed, as Hibernate will handle everything for us. Now, let's use the getAll()
method implemented in the previous chapter to see the changes:
Thanks for your feedback!