AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

OneToMany Relationship

In this post, we are going to show an example of a OneToMany relationship in Hibernate, where we will see how we can map this type of relationship using JPA.

In a previous article, we have already seen some options with Hibernate, such as soft delete and inheritance, both of which are very important features for simplifying coding and maintaining cleanliness in our applications.

How does a One-to-Many Relationship work?

For example, a bank has many bank accounts belonging to different customers. In this case, we can say that we have a 1..N relationship. That means the one-to-many relationship signifies that a column in one database table is mapped to multiple columns in another table. Let’s see it visually:

Example of OneToMany Relationship in Hibernate
Relationship OneToMany in Hibernate

OneToMany Relationship in Hibernate

The above relationship reflects a 1..N relationship, where a bank has 1 to N bank accounts. In the Bank Account table, we will have a column that references the bank through a foreign key.

When transforming our relational model to Java, we will add an object of the main class in our secondary class. In other words, in our BankAccount class, we will add an object of the Bank class using the @ManyToOne annotation. This annotation allows us to map the column with the foreign key in our table, so the other entity will have an object reference to its main entity. This is generally the most efficient way to map an association.

On the other hand, we also have the @OneToMany annotation provided by JPA as a dirty checking mechanism, so when applied, we will have a collection of objects in the main class.

Depending on the needs of our application or project, we can apply one of the following approaches:

  • Unidirectional relationship with @OneToMany
  • Bidirectional relationship with @OneToMany

Bidirectional Relationship in @OneToMany

In this section, we will see how to define our entities to work bidirectionally using the two annotations we saw earlier, @OneToMany and @ManyToOne.

This type of relationship is the best approach when we want to create a OneToMany relationship and we need a collection of child objects in the parent.

Let’s see an example:

@Getter
@Setter
@Entity
@NoArgsConstructor
public class Bank {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy="bank")
    private Set<BankAccount> bankAccounts;

    public void addBankAccount(BankAccount bankAccount) {
        bankAccounts.add(bankAccount);
        bankAccount.setBank(this);
    }

    public void removeComment(BankAccount bankAccount) {
        bankAccounts.remove(bankAccount);
        bankAccount.setBank(null);
    }
}
@Getter
@Setter
@Entity
@NoArgsConstructor
public class BankAccount {

    private Long id;

    private Long user;

    private BigDecimal amount;

    private LocalDate date;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="bank_id", nullable=false)
    private Bank bank;
}

As we can see, we have used the mentioned annotations to maintain a bidirectional relationship, and we have also added some additional details that we will explore:

  • Use of Lazy: In the @ManyToOne relationship, we have used lazy loading to avoid eager fetching and fetching everything, which could affect the performance of our application.
  • Bidirectional association: To keep both sides synchronized, we need to add and remove elements from our bankAccounts collection to maintain a stable relationship. That’s why we use the addBankAccount and removeBankAccount methods.
  • Use of Cascade ALL: We make use of the cascade feature with the ALL option to propagate operations in any situation.

Let’s execute the following test to see the result of inserting a bank with bankAccounts:

@SpringBootTest
public class BankAccountRepositoryTest {

    @Autowired
    private BankAccountRepository bankAccountRepository;

    @Autowired
    private BankRepository bankRepository;

    @Test
    public void when_save_new_bank_with_multiples_accounts_then_bank_is_saved_correctly() {

        Bank bank = new Bank();
        bank.setName("TGB");

        var bankAccount = new BankAccount();
        bank.addBankAccount(bankAccount);

        bankRepository.save(bank);

    }
}

When executing the test, we can observe that two inserts will be performed. The first one is to create a new row in the “bank” table, and the second one is to create a “bankAccount” associated with the bank.

Hibernate: 
    insert 
    into
        Bank
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        BankAccount
        (amount, bank_id, user, id) 
    values
        (?, ?, ?, ?)

As we mentioned before, this approach is the best when we want the parent object to have a collection of child objects.

Unidirectional Relationship in @OneToMany with Three Tables

For a unidirectional relationship with @OneToMany, we will remove the @OneToMany annotation in order to remove the parent object in the child class. Let’s see the example based on the previously created entities:

@Getter
@Setter
@Entity
@NoArgsConstructor
public class Bank {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy="bank")
    private Set<BankAccount> bankAccounts;

    public void addBankAccount(BankAccount bankAccount) {
        bankAccounts.add(bankAccount);
        bankAccount.setBank(this);
    }

    public void removeComment(BankAccount bankAccount) {
        bankAccounts.remove(bankAccount);
        bankAccount.setBank(null);
    }
}
@Getter
@Setter
@Entity
@NoArgsConstructor
public class BankAccount {

    private Long id;

    private Long user;

    private BigDecimal amount;

    private LocalDate date;

}

To create the unidirectional relationship, what we have done is remove the mapping that would be done in the BankAccount table, thus losing the foreign key.

Let’s see with a test how the result of this execution would look like.

@SpringBootTest
public class BankRepositoryTest {

    @Autowired
    private BankRepository bankRepository;

    @Test
    public void when_save_new_bank_with_multiples_accounts_then_bank_is_saved_correctly() {

        Bank bank = new Bank();
        bank.setName("TGB");

        var bankAccount = new BankAccount();
        bank.getBankAccounts().add(bankAccount);

        bankRepository.save(bank);

    }
}

In the previous test, we only created one bank and one bankAccount associated with the bank. Let’s see the result of the execution:

Hibernate: 
    insert 
    into
        Bank
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        BankAccount
        (amount, user, id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        Bank_bankAccounts
        (Bank_id, bankAccounts_id) 
    values
        (?, ?)

Upon analyzing the result of the execution, we can see that three different inserts have been made: one in the “Bank” table, another in the “BankAccount” table, and a third one in the “Bank_bankAccounts” table. As you can see, this table does not belong to our original model. It is a new table that has been formed with the keys from the “Bank” and “BankAccount” tables.

When creating this type of unidirectional relationship, an intermediate table is created, resulting in a representation of an N..M relationship, a many-to-many relationship.

Relationship N..M
Relationship N..M

The problem with this approach is the use of more resources and poorer performance when performing any query or insertion.

To avoid this issue, we can add the @JoinColumn annotation, which will eliminate the created intermediate table. Let’s see how it would look like:

Unidirectional Relationship in @OneToMany with Two Tables

In the previous section, we saw how to create a unidirectional relationship using the @OneToMany annotation, but we encountered the problem of creating an additional table. The creation of an additional table ultimately leads to poorer performance in our applications. To solve this problem, we will use the @JoinColumn annotation.

The @JoinColumn annotation helps us indicate that we want a foreign key in the child table that defines the relationship.

@Getter
@Setter
@Entity
@NoArgsConstructor
public class Bank {

    @Id
    @GeneratedValue

    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "bank_id")
    private Set<BankAccount> bankAccounts = new HashSet<>();

}

Let’s execute a test to see the result of adding @JoinColumn.

Hibernate: 
    insert 
    into
        Bank
        (name, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        BankAccount
        (amount, user, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        BankAccount 
    set
        bank_id=? 
    where
        id=?

We can see that by adding the @JoinColumn annotation, we no longer have an intermediate table. However, the performance is not completely optimal as we first perform inserts and then update the foreign key.

The same process would occur for deletion as well.

Example of OneToMany Relationship in Hibernate in a Bidirectional way

Next, we will define an example of a OneToMany relationship in Hibernate using Spring Boot. This example will be based on a bidirectional relationship, which is likely the most optimal approach. To do this, we will start by creating the project using the Initializr page.

In the following sections, we will gradually create the necessary layers of our application.

The dependencies we will select are Spring Web, Spring Data, Lombok, and H2.

  • Spring Data includes all the persistence part, incorporating Hibernate + JPA.
  • Lombok will help us reduce the boilerplate code.
  • H2 is an in-memory database perfect for testing, not recommended for production environments.

To see the complete example, where you can see both the unidirectional and bidirectional relationships, click here.

Maven Dependencies.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

H2 Database Configuration

Next, we will add the necessary configuration to use an in-memory H2 database. It is important to note that in order to access the H2 console, it needs to be enabled using the property h2.console.enabled.

spring:
  application:
    name: one-to-many
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password: password
    initialize: true
    initialization-mode: always
    hikari:
      connection-timeout: 6000
      initialization-fail-timeout: 0
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        enable_lazy_load_no_trans: true


  h2:
    console:
      enabled: true

In addition, we are going to perform data import by adding a file named import.sql in our resources folder.

INSERT INTO BANKACCOUNT VALUES (1,1, 1.20,1);

INSERT INTO BANK VALUES (1,'TBC');

Entity Creation and OneToMany Relationship

Let’s create two classes, Bank and BankAccount, with a bidirectional relationship between them. To do this, we will use the @Entity annotation to indicate that both classes are entities. And through the @OneToMany and @ManyToOne annotations, we will express a bidirectional relationship between the two classes.

@Getter
@Setter
@Entity
@NoArgsConstructor
public class BankAccountBidirectional {

    @Id
    @GeneratedValue
    private Long id;

    private Long user;

    private BigDecimal amount;

    @ManyToOne
    private BankBidirectional bank;

}
@Getter
@Setter
@Entity
@NoArgsConstructor
public class BankBidirectional {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "bank", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<BankAccountBidirectional> bankAccounts;

    public void addBankAccount(BankAccountBidirectional bankAccount) {
        if (null == bankAccounts) {
            bankAccounts = new HashSet<>();
        }
        bankAccounts.add(bankAccount);
        bankAccount.setBank(this);
    }

    public void removeComment(BankAccountBidirectional bankAccount) {
        bankAccounts.remove(bankAccount);
        bankAccount.setBank(null);
    }
}

As mentioned earlier when explaining the bidirectional relationship, a foreign key will be created in the BankAccount child table in our database. Additionally, to maintain the integrity of our database, we will add the remove and add methods.

Repository Creation

To create a repository for Bank, we will use JpaRepository provided by Spring Data. This class will allow us to use all the necessary methods and actions to perform operations in our database.

public interface BankBidirectionalRepository extends JpaRepository<BankBidirectional, Long> {

}

Bank Service Creation

We are going to create a service layer that will act as a pass-through between the controller and the repository. In other words, the controller will make a call to the service, and the service will then call the repository to operate with the database.

In the service, we will perform dependency injection of BankAccountBidirectionalRepository.

@RequiredArgsConstructor
@Service
public class BankAccountService {

    private final BankAccountBidirectionalRepository bankAccountRepository;

    public BankAccountBidirectional findById(Long id) {

        return bankAccountRepository.findById(id).orElseThrow();

    }

    public List<BankAccountBidirectional> findAll() {

        return bankAccountRepository.findAll();
    }
}

Controller Creation in a OneToMany Application

We are going to create our controller class to access our application. The controller will have two endpoints, one to retrieve all accounts and another to retrieve accounts by their ID.

@RestController
@RequiredArgsConstructor
@RequestMapping("/banks")
public class BankAccountController {


    private final BankAccountService bankAccountService;

    @GetMapping
    public ResponseEntity<List<BankAccountBidirectional>> getBankAccounts() {

        var bankAccounts = bankAccountService.findAll();

        return new ResponseEntity<>(bankAccounts, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<BankAccountBidirectional> getBankAccountById(@PathVariable Long id) {

        var bankAccount = bankAccountService.findById(id);

        return new ResponseEntity<>(bankAccount, HttpStatus.OK);

    }

}

Conclusion

In this example of OneToMany relationship in Hibernate, we have seen the different ways we can create this type of relationship.

On one hand, we have explored the two forms of using the unidirectional relationship, which can have an impact on the performance of our application. On the other hand, we have seen the bidirectional approach, which is the most commonly used, frequently encountered in applications, and provides better performance.

You can find the complete example here.

If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!

Leave a Reply

Your email address will not be published. Required fields are marked *