1. Overview

Java Persistence API (JPA) is an Object-Relational Mapping (ORM) specification for Java applications. Further, Hibernate is one of the popular implementations of the JPA specification.

Associations are a fundamental concept in ORM, allowing us to define relationships between entities. In this tutorial, we’ll discuss the differences between unidirectional and bidirectional associations in JPA/Hibernate.

2. Unidirectional Associations

Unidirectional associations are commonly used in object-oriented programming to establish relationships between entities. However, it’s important to note that in a unidirectional association, only one entity holds a reference to the other.

To define a unidirectional association in Java, we can use annotations such as @ManyToOne, @OneToMany, @OneToOne, and @ManyToMany. By using these annotations, we can create a clear and well-defined relationship between two entities in our code.

2.1. One-To-Many Relationship

In a one-to-many relationship, an entity has a reference to one or many instances of another entity.

A common example is the relationship between a Department and its Employees. Each Department has many Employees, but each Employee belongs to one Department only.

Let’s take a look at how to define a one-to-many unidirectional association:

@Entity
public class Department {
 
    @Id
    private Long id;
 
    @OneToMany
    @JoinColumn(name = "department_id")
    private List<Employee> employees;
}

@Entity
public class Employee {
 
    @Id
    private Long id;
}

Here, the Department entity has a reference to a list of Employee entities. The @OneToMany annotation specifies that this is a one-to-many association. The @JoinColumn annotation specifies the foreign key column in the Employee table referencing the Department table.

2.2. Many-To-One Relationship

In a many-to-one relationship, many instances of an entity are associated with one instance of another entity.

For example, let’s consider Student and School. Each Student can be enrolled in one School only, but each School can have multiple Students.

Let’s take a look at how to define a many-to-one unidirectional association:

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "school_id")
    private School school;
}

@Entity
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

In this case, we have a many-to-one unidirectional association between Student and School entities. The @ManyToOne annotation specifies that each student can be enrolled in only one school, and the @JoinColumn annotation specifies the foreign key column name to join the Student and School entities.

2.3. One-To-One Relationship

In a one-to-one relationship, an instance of an entity is associated with only one instance of another entity.

A common example is the relationship between an Employee and a ParkingSpot. Each Employee has a ParkingSpot, and each ParkingSpot belongs to one Employee.

Let’s take a look at how to define a one-to-one unidirectional association:

@Entity
public class Employee {
 
    @Id
    private Long id;
 
    @OneToOne
    @JoinColumn(name = "parking_spot_id")
    private ParkingSpot parkingSpot;
 
}

@Entity
public class ParkingSpot {
 
    @Id
    private Long id;
 
}

Here, the Employee entity has a reference to the ParkingSpot entity. The @OneToOne annotation specifies that this is a one-to-one association. The @JoinColumn annotation specifies the foreign key column in the Employee table that references the ParkingSpot table

2.4. Many-To-Many Relationship

In a many-to-many relationship, many instances of an entity are associated with many instances of another entity.

Suppose we have two entities – Book and Author. Each Book can have multiple Authors, and each Author can write multiple Books.  In JPA, this relationship is represented using the @ManyToMany annotation.

Let’s take a look at how to define a many-to-many unidirectional association:

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToMany
    @JoinTable(name = "book_author",
            joinColumns = @JoinColumn(name = "book_id"),
            inverseJoinColumns = @JoinColumn(name = "author_id"))
    private Set<Author> authors;

}

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

Here, we can see a many-to-many unidirectional association between Book and Author entities. The @ManyToMany annotation specifies that each Book can have multiple Authors, and each Author can write multiple Books. The @JoinTable annotation specifies the name of the join table and the foreign key columns to join the Book and Author entities.

3. Bidirectional Associations

 A bidirectional association is a relationship between two entities where each entity has a reference to the other.

In order to define bidirectional associations, we use the mappedBy attribute in the @OneToMany and @ManyToMany annotations. However, it’s important to note that only relying on unidirectional associations may not be sufficient, as bidirectional associations provide additional benefits.

3.1. One-To-Many Bidirectional Association

In a one-to-many bidirectional association, an entity has a reference to another entity. Additionally, the other entity has a collection of references to the first entity.

For instance, a Department entity has a collection of Employee entities. Meanwhile, an Employee entity has a reference to the Department entity it belongs.

Let’s take a look at how to create a one-to-many bidirectional association:

@Entity
public class Department {
 
    @OneToMany(mappedBy = "department")
    private List<Employee> employees;
 
}
 
@Entity
public class Employee {
 
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;
 
}

In the Department entity, we use the @OneToMany annotation to specify the relationship between the Department entity and the Employee entity. The mappedBy attribute specifies the name of the attribute in the Employee entity that owns the relationship. In this case, the Department entity doesn’t own the relationship, so we specify mappedBy = “department”.

In the Employee entity, we use the @ManyToOne annotation to specify the relationship between the Employee entity and the Department entity. The @JoinColumn annotation specifies the name of the foreign key column in the Employee table referencing the Department table.

3.2. Many-To-Many Bidirectional Association

When dealing with a many-to-many bidirectional association, it’s important to understand that each entity involved will have a collection of references to the other entity.

To illustrate this concept, let’s consider the example of a Student entity that has a collection of Course entities and a Course entity that in turn has a collection of Student entities. By establishing such a bidirectional association, we enable both entities to be aware of each other and make it easier to navigate and manage their relationship.

Here’s an example of how to create a many-to-many bidirectional association:

@Entity
public class Student {
 
    @ManyToMany(mappedBy = "students")
    private List<Course> courses;
 
}
 
@Entity
public class Course {
 
    @ManyToMany
    @JoinTable(name = "course_student",
        joinColumns = @JoinColumn(name = "course_id"),
        inverseJoinColumns = @JoinColumn(name = "student_id"))
    private List<Student> students;
 
}

In the Student entity, we use the @ManyToMany annotation to specify the relationship between the Student entity and the Course entity. The mappedBy attribute specifies the attribute’s name in the Course entity that owns the relationship. In this case, the Course entity owns the relationship, so we specify mappedBy = “students”.

In the Course entity, we use the @ManyToMany annotation to specify the relationship between the Course entity and the Student entity*.* The @JoinTable annotation specifies the name of the join table that stores the relationship.

4. Unidirectional vs. Bidirectional Association

Unidirectional and bidirectional associations in object-oriented programming differ in the direction of the relationship between the two classes.

Firstly, unidirectional associations only have a relationship in one direction, whereas bidirectional associations have a relationship in both directions. This difference can impact the design and functionality of software systems. For example, bidirectional associations can make it easier to navigate between related classes, but they can also introduce more complexity and potential for errors.

On the other hand, unidirectional associations can be simpler and less error-prone, but they may require more workarounds to navigate between related classes.

Overall, understanding the differences between unidirectional and bidirectional associations is crucial for making informed decisions about the design and implementation of software systems.

Here’s a table summarizing the differences between unidirectional and bidirectional associations in a database:

Unidirectional Association

Bidirectional Association

Definition

A relationship between two tables where one table has a foreign key that references the primary key of another table.

A relationship between two tables where both tables have a foreign key that references the primary key of the other table.

Navigation

Only navigable in one direction – from the child table to the parent table.

Navigable in both directions – from either table to the other.

Performance

Generally faster due to simpler table structure and fewer constraints.

Generally slower due to additional constraints and table structure complexity.

Data Consistency

Ensured by the foreign key constraint in the child table referencing the primary key in the parent table.

Ensured by the foreign key constraint in the child table referencing the primary key in the parent table.

Flexibility

Less flexible as changes in the child table may require changes to the parent table schema.

More flexible as changes in either table can be made independently without affecting the other.

Notably, the specifics of implementation can vary depending on the database management system used. However, to provide a general understanding, the table above presents an overview of the differences between unidirectional and bidirectional associations.

It’s important to recognize these variations as they can significantly impact the performance and functionality of the database system.

5. Conclusion

In this article, we saw how the choice between unidirectional or bidirectional associations relies on the software’s specific demands. Unidirectional associations are simpler and may be sufficient for many applications, while bidirectional associations provide more flexibility and can be useful in more complex scenarios.

However, bidirectional associations can also introduce more complexity and potential issues, such as circular dependencies and memory leaks, and should be used with caution. It’s important to carefully consider the trade-offs and choose the appropriate type of association for each situation.

As always, the code is available over on GitHub.