Thursday, February 17, 2022

One-To-One Bidirectional Relationship In Spring Boot

springboot,java,hibernate,jpa,onetoone,bidirectional,programming,software development,technology
One-to-one relationships can be unidirectional or bidirectional. In the previous tutorial, we learned about Hibernate's unidirectional One-To-One relationship. We will implement a bidirectional relationship in the One-To-One association in this tutorial.

1. What is the bidirectional One-To-One association

In a bidirectional relationship, we can obtain information about two related entities by querying each one separately. To put it another way, we can move from one entity to another and vice versa. Consider the Book and Publisher examples. A well-known author gave his or her book to a publisher for publication, or the publisher used to publish a well-known author's book. Consider the definitions of Publisher and Book.
 
// Publisher class
  public class Publisher {
      private int id = 0;
      …
      private Book book;
      …
  }
// Book class
  public class Book {
      private int id = 0;
      …
      private Publisher publisher;
      …
  }

We declared a reference of the Book class in the Publisher class to maintain the bidirectional relationship in the One-To-One association. So, if we query Publisher, we can get Book, and if we query Book, we can get Publisher. Consider the case of the Owner and House. We can obtain information about the house by contacting the owner, and vice versa.
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology


👉 So, in this tutorial, we will learn about the One-To-One bidirectional relationship using the Owner and House entities as examples.

2. Spring Boot project creation

We will use Spring Initializr to create a new Spring Boot project, which will generate a basic structure for our Spring Boot project. The following dependencies have been added:
  • Spring Boot DevTools - necessary development tools  
  • Spring Web - for Spring MVC and embedded Tomcat that will run the Spring Boot application  
  • Spring Data JPA - Java Persistence API
  • MySQL Driver - JDBC driver for MySQL (for other DB you have to choose that dependency for that DB)
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology
Then, to download the project zip file, click GENERATE. Unzip the zip archive. Import the project as a Maven project into Eclipse/STS.

3. Connect to the Database

We'll put the connection information in the application because we're using MySQL as our database. Hibernate will use this information to connect to the database as the application.properties file has a name/value pair. The connection information is described in the following snippet:
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology
Here, we've set datasource.url to the URL of our JDBC connection. The database credentials are mentioned in datasource.user and datasource.password.

Spring Boot can gather the necessary information about the database from the connection URL, so it is not necessary to specify datasource.driver-class-name. However, we will be safer if we specify the driver-class-name.

When the application is running, jpa.show-sql displays Hibernate SQL queries in the console, jpa.hibernate.ddl-auto is set to update, which updates the database schema every time we restart the application, and hibernate.dialect indicates which database dialect we are using.


👉 We'll make two entities to demonstrate: Owner and House. Then, between these two entities, establish a One-To-One bidirectional relationship.

4. Entities

Create an entity package and include the Owner class in it. Include this code snippet:
@Entity
@Table(name = "OWNER")
public class Owner {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int Id;

  @Column(name = "owner_name")
  private String ownerName = "";

  @OneToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = "house_id")
  private House house = null;

  public Owner() {}

  public Owner(String ownerName) { this.ownerName = ownerName; }

  public int getId() { return Id;b}
  public void setId(int id) { Id = id; }

  public String getOwnerName() { return ownerName; }
  public void setOwnerName(String ownerName) { this.ownerName = ownerName; }

  public House getHouse() { return house; }
  public void setHouse(House house) { this.house = house; }

  @Override
  public String toString() {
    return "Owner{" + "Id=" + Id + ", ownerName='" + ownerName + '\'' + ", house=" + house + '}';
  }
}
The Owner class has a One-To-One relationship with the House entity, as indicated by the column name house_id.

Create the House class in the entity package:
@Entity
@Table(name = "HOUSE")
public class House {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int Id;

    @Column(name = "no_of_floor")
    private int noOfFloor = 0;

    @Column(name = "no_of_bed_room")
    private int noOfBedRoom = 0;

    @Column(name = "area")
    private String area = "";

    @OneToOne(mappedBy = "house", cascade = CascadeType.ALL)
    private Owner owner = null;

    public House() {}

    public House(int noOfFloor, int noOfBedRoom, String area) {
        this.noOfFloor = noOfFloor;
        this.noOfBedRoom = noOfBedRoom;
        this.area = area;
    }

    public int getId() { return Id; }
    public void setId(int id) { Id = id; }

    public int getNoOfFloor() { return noOfFloor; }
    public void setNoOfFloor(int noOfFloor) { this.noOfFloor = noOfFloor; }

    public int getNoOfBedRoom() { return noOfBedRoom; }
    public void setNoOfBedRoom(int noOfBedRoom) { this.noOfBedRoom = noOfBedRoom; }

    public String getArea() { return area; }
    public void setArea(String area) { this.area = area; }

    public Owner getOwner() { return owner; }
    public void setOwner(Owner owner) { this.owner = owner; }

    @Override
    public String toString() {
        return "House{" + "Id=" + Id + ", noOfFloor=" + noOfFloor + ", noOfBedRoom=" + noOfBedRoom +
                ", area='" + area + '\'' + ", owner=" + owner + '}';
    }
}
The House class has a One-To-One relationship with the Owner entity as well.

In other words, we created a reference to the House in the Owner entity and a reference to the Owner in the House entity.    

But WHY? Because we are creating a bidirectional relationship between the Owner and the House entity. 

And HOW? We used mappedBy = "house" in the @OneToOne annotation in the House class to set the connection with the House in the Owner entity. As a result, Hibernate will map the House with the Owner using the (reference) name house.
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

The relationship is as follows in the database:
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology


5. Repositories

The repository in Spring Boot is the data access layer that allows us to interact with our real database for operations like insert, update, and delete using Spring Data JPA. We have significantly reduced the number of boilerplate codes required to perform database operations as our OwnerRepository and HouseRepository extend JpaRepository.  

Make a package called repository. Create the OwnerRepository interface in it and include the following code snippet:
public interface OwnerRepository extends JpaRepository<Owner, Integer> {
}

Create a HouseRepository interface in the repository package:
public interface HouseRepository extends JpaRepository<House, Integer> {
}

6. Controller

A controller is a class that has one or more public methods. Controllers are typically placed in the Controller directory. If a class is annotated with @Controller or @RestController in Spring Boot, it will serve as a controller, and its public methods will be exposed as HTTP endpoints if they are annotated with @PostMapping or @GetMapping.

As a result, an HTTP GET request to http://localhost:PORT/method-name invokes the @GetMapping method of the ExampleController class.

Create a package called controller and create an OwnerController class in it. The OwnerController consists of two methods - one is to save the Owner with House and the other is to fetch Owner details by id. Code as follows:
@RestController
@RequestMapping("/owner")
public class OwnerController {

    @Autowired
    private OwnerRepository ownerRepository;

    @PostMapping("/saveOwner")
    public Owner saveOwner(@RequestBody Owner owner) {
        System.out.println("Owner save called...");
        Owner ownerOut = ownerRepository.save(owner);
        System.out.println("Saved Owner :: " + ownerOut);

        return ownerOut;
    }

    @GetMapping("/getOwner/{id}")
    public String getOwner(@PathVariable(name = "id") String id) {
        System.out.println("Owner get called...");
        Owner ownerOut = ownerRepository.getById(Integer.valueOf(id));
        System.out.println("Owner with house :: " + ownerOut);

        return "Owner fetched...";
    }
}

7. Run the Project

To run the project in Visual Studio Code, follow these steps:
  1. Open SpringbootmanytomanyApplication.java.
  2. Click on Run to run the Java program.
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

To run the project in Eclipse, follow these steps:
  1. Right-click on SpringbootmanytomanyApplication.java.
  2. Then choose Run As, then click on Spring Boot App.
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

Look at the audit log in the console (Eclipse/VS Code): as you can see, Hibernate created two tables - HOUSE and OWNER - with specified column names and modified the OWNER table to add a foreign key that references the HOUSE table.

JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

Because we did not specify a port in the application.properties file, our project will run on port 8080, and the default URL to access any REST API in this project is http://localhost:8080/. Because our project runs on our local machine, we used localhost. If your project is running on a remote server or in EC2, you must use the remote server's or EC2's IP address or the elastic IP address.

8. Testing of REST APIs

Now we'll put those REST APIs in the OwnerController and HouseController to the test.

👉 Create a new Owner

Using cURL, we call the following URL to create a new owner:
http://localhost:8080/owner/saveOwner
curl --location --request POST 'http://localhost:8080/owner/saveOwner' 
--header 'Content-Type: application/json' 
--data-raw '{
    "ownerName":"Snow White",
    "house":{
        "noOfFloor":2,
        "noOfBedRoom":4,
        "area":"19  50 sqft"
    }
}'

Let’s see the audit logs in Console (Eclipse/STS):
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

Hibernate saves House data first, followed by Owner data, as shown in the Console. Hibernate obtains the value of the primary key column (id) from the House table and saves it in the Owner table's foreign key column (house_id).

👉 Fetch Owner Details

We use cURL and an id value of 2 to call the following URL to retrieve a specific owner's details as well as his/her house details:
curl --location --request GET 'http://localhost:8080/owner/getOwner/2'
And see the audit logs at the Console:
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology

When we called /getMotorcycle/id in the previous article, Hibernate generated a select SQL query by joining the MOTOR_CYCLE and ENGINE tables to retrieve data. When entities have bidirectional relationships, however, Hibernate creates two separate select SQL queries to retrieve Owner and House data from the database. We have retrieved House information that we have associated with the Owner in this manner.

Controller - Modification

And HouseController has a method for retrieving House details based on the id. The code is as follows:
@RestController
@RequestMapping("/house")
public class HouseController {

    @Autowired
    private HouseRepository houseRepository;

    @GetMapping("/getHouse/{id}")
    public String getHouse(@PathVariable(name = "id") String id) {
        System.out.println("House get called...");
        House houseOut = houseRepository.getById(Integer.valueOf(id));
        System.out.println("House :: " + houseOut);
        System.out.println("Owner :: " + houseOut.getOwner());

        return "House fetched...";
    }
}
In this case, we get the House details first, then the Owner details by calling the House entity's getOwner() method.

Testing - Again

👉 Fetch House Details

To retrieve specific house details as well as the owner, we use cURL and an id value of 2 to call the following URL:
curl --location --request GET 'http://localhost:8080/house/getHouse/2'
See the audit logs at the Console:
JAVA,Hibernate,bidirectional,Programming,Spring Boot,RestController,OneToOne,Software Development,Technology
According to the audit logs, we retrieved the Owner details from the database by querying the House entity.

9. Conclusion

So, using bidirectional relationships, we can retrieve one entity from another and vice versa. When creating or updating entities, remember to manage both sides of the relationship to ensure data consistency.

You can download the source code.
Happy coding!!! ðŸ˜Š
in


No comments:

Post a Comment

Popular posts