Master Bidirectional One-to-One Relations in 5 Steps: Boost Spring Data JPA Efficiency

In this section, we will delve into the following topics:

Unlocking the Power of Bidirectional One-to-One Relations

In this in-depth guide, we'll explore the intricacies of mutual one-to-one associations, CRUD operations, and the role of mappedBy, @JsonManagedReference, and @JsonBackReference in efficient data modeling.

  • Understanding Mutual One-to-One Associations
  • Streamlining CRUD Operations
  • The Importance of mappedBy
  • Demystifying @JsonManagedReference
  • Unlocking the Potential of @JsonBackReference

Through a concise example, we'll demonstrate how to seamlessly integrate these concepts, starting with entity definition.

Let's begin by modeling our entities.Entity ModelingNext, we'll examine how Hibernate generates the tables.

Hibernate-generated TablesIn this example, we've designated Address as the owning side of the one-to-one relationship, with Organization as the referencing side. This approach ensures that the foreign key relationship is established in both the address and organization tables. Now, let's delve into the code. We'll utilize the mappedBy attribute in conjunction with the @OneToOne annotation to define this relationship. The mappedBy attribute specifies the referencing side of the relationship, indicating to Hibernate that the relationship’s key resides on the other side. To master bidirectional one-to-one relations and unlock Spring Data JPA's full potential, visit t8tech.com.

Organization Entity

package com.notyfyd.entity;import javax.persistence.*;@Entity@Table(name = "t_organization")public class Organization {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Long entityId;    private String companyName;    private String organizationCode;    @OneToOne(targetEntity = Address.class, cascade = CascadeType.ALL)    private Address headquarters;    public Long getEntityId() {        return this.entityId;    }    public void setEntityId(Long entityId) {        this.entityId = entityId;    }    public String getCompanyName() {        return this.companyName;    }    public void setCompanyName(String companyName) {        this.companyName = companyName;    }    public String getOrganizationCode() {        return this.organizationCode;    }    public void setOrganizationCode(String organizationCode) {        this.organizationCode = organizationCode;    }    public Address getHeadquarters() {        return this.headquarters;    }    public void setHeadquarters(Address headquarters) {        this.headquarters = headquarters;    }}

Institutional Address Entity

package com.notyfyd.entity;import javax.persistence.*;@Entity@Table(name = "t_address")public class Address {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Long id;    private String building;    private String street;    private String city;    private String state;    private String country;    private String zipcode;    @OneToOne(targetEntity = Organization.class, mappedBy = "address")    private Organization organization;    public Long getId() {        return this.id;    }    public void setId(Long id) {        this.id = id;    }    public String getBuilding() {        return this.building;    }    public void setBuilding(String building) {        this.building = building;    }    public String getStreet() {        return this.street;    }    public void setStreet(String street) {        this.street = street;    }    public String getCity() {        return this.city;    }    public void setCity(String city) {        this.city = city;    }    public String getState() {        return this.state;    }    public void setState(String state) {        this.state = state;    }    public String getCountry() {        return this.country;    }    public void setCountry(String country) {        this.country = country;    }    public String getZipcode() {        return this.zipcode;    }    public void setZipcode(String zipcode) {        this.zipcode = zipcode;    }    public Organization getOrganization() {        return organization;    }    public void setOrganization(Organization organization) {        this.organization = organization;    }}

@OneToOne(targetEntity = Organization.class, mappedBy = "address")

private Organization organization;

In this particular scenario, the mappedBy attribute is invariably set to “parent”, implying that Address will assume the role of the owning side, while Organization will serve as the inverse reference.

Address Repository Module

package com.notyfyd.repository;import com.notyfyd.entity.Address;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface AddressRepository extends JpaRepositoryAddress, Long {}

Organization Repository Module

package com.notyfyd.repository;import com.notyfyd.entity.Organization;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface OrganizationRepository extends JpaRepositoryOrganization, Long {}

Address Management Controller

@RestControllerpublic class AddressController {    @Autowired    private AddressRepository addressRepository;        @GetMapping("/address/retrieve/all")    public ListAddress retrieveAllAddresses() {        return addressRepository.findAll();    }}

Organization Management Controller

package com.notyfyd.controller;import com.notyfyd.entity.Organization;import com.notyfyd.repository.OrganizationRepository;import com.notyfyd.service.OrganizationService;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.List;@RestControllerpublic class OrganizationController {    private OrganizationService organizationService;    private OrganizationRepository organizationRepository;    public OrganizationController(OrganizationService organizationService, OrganizationRepository organizationRepository) {        this.organizationService = organizationService;        this.organizationRepository = organizationRepository;    }    @PostMapping("/organization/create")    public ResponseEntitylt;Objectgt; createOrganization(@RequestBody Organization organization) {        return organizationService.createOrganization(organization);    }    @DeleteMapping("/organization/delete/{id}")    public ResponseEntitylt;Objectgt; deleteOrganization(@PathVariable Long id) {        if(organizationRepository.findById(id).isPresent()) {            organizationRepository.deleteById(id);            if (organizationRepository.findById(id).isPresent())                return ResponseEntity.unprocessableEntity().body("Failed to delete the specified organization");            else return ResponseEntity.ok("Successfully deleted the specified organization");        } else return ResponseEntity.unprocessableEntity().body("Specified organization not present");    }    @GetMapping("/organization/get/{id}")    public Organization getOrganization(@PathVariable Long id) {        if(organizationRepository.findById(id).isPresent())            return organizationRepository.findById(id).get();        else return null;    }    @GetMapping("/organization/get")    public Listlt;Organizationgt; getOrganizations() {        return organizationRepository.findAll();    }    @PutMapping("/organization/update/{id}")    public ResponseEntitylt;Objectgt; updateOrganization(@PathVariable Long id, @RequestBody Organization org) {        return organizationService.updateOrganization(id, org);    }}

Comprehensive Organizational Assistance Program

package com.notyfyd.service;import com.notyfyd.entity.Address;import com.notyfyd.entity.Organization;import com.notyfyd.repository.AddressRepository;import com.notyfyd.repository.OrganizationRepository;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Servicepublic class OrganizationService {    private OrganizationRepository organizationRepository;    private AddressRepository addressRepository;    public OrganizationService(OrganizationRepository organizationRepository, AddressRepository addressRepository) {        this.organizationRepository = organizationRepository;        this.addressRepository = addressRepository;    }    @Transactional    public ResponseEntitylt;Objectgt; createOrganization(Organization organization) {        Organization org = new Organization();        org.setName(organization.getName());        org.setOrgId(organization.getOrgId());        org.setAddress(organization.getAddress());        Organization savedOrg = organizationRepository.save(org);        if(organizationRepository.findById(savedOrg.getId()).isPresent())            return ResponseEntity.ok().body("Organization created successfully.");        else return ResponseEntity.unprocessableEntity().body("Failed to create the organization specified.");    }    @Transactional    public ResponseEntitylt;Objectgt; updateOrganization(Long id, Organization org) {        if(organizationRepository.findById(id).isPresent()) {            Organization organization = organizationRepository.findById(id).get();            organization.setName(org.getName());            organization.setOrgId(org.getName());            Address address = addressRepository.findById(organization.getAddress().getId()).get();            address.setBuilding(organization.getAddress().getBuilding());            address.setStreet(organization.getAddress().getStreet());            address.setCity(organization.getAddress().getCity());            address.setState(organization.getAddress().getState());            address.setCountry(organization.getAddress().getCountry());            address.setZipcode(organization.getAddress().getZipcode());            Address savedAddress =  addressRepository.save(address);            organization.setAddress(savedAddress);            Organization savedOrganization = organizationRepository.save(organization);            if(organizationRepository.findById(savedOrganization.getId()).isPresent())                return ResponseEntity.ok().body("Successfully Updated Organization");            else return ResponseEntity.unprocessableEntity().body("Failed to update the specified Organization");        } else return ResponseEntity.unprocessableEntity().body("The specified Organization is not found");    }}

Configuring the Application

server.port=2003spring.datasource.driver-class-name= org.postgresql.Driverspring.datasource.url= jdbc:postgresql://192.168.64.6:30432/jpa-testspring.datasource.username = postgresspring.datasource.password = rootspring.jpa.show-sql=truespring.jpa.hibernate.ddl-auto=create

Now, let’s initiate the application. Open Postman and create a new organization using the JSON object provided below.

You can access the source code for this project at https://github.com/gudpick/jpa-demo/tree/one-to-one-bidirectional-starter.

{	"name": "Nooble Academy",	"orgId": "NAL",	"address": {		"building": "XII/706-A",		"street": "Andheri",		"city": "Mumbai",		"state": "Maharashtra",		"zipcode": "400703",		"country": "India"	}}

When you send a POST request, you will observe that the tables are populated correctly. However, the GET request will result in a stack overflow error. Previously, we resolved this issue by utilizing @JsonIgnore to handle the circular reference. Now, let’s attempt to rectify this error using @JsonManagedReference and @JsonBackReference.

Please find the code snippets below.

Address Entity Configuration

@JsonManagedReference@OneToOne(targetEntity = Organization.class, mappedBy = "address")private Organization organization;

The @JsonManagedReference annotation is employed on the child reference, while @JsonBackReference is utilized in the corresponding child class (Organization) to handle the circular reference.

Organization Entity Configuration

@JsonBackReference@OneToOne(targetEntity = Address.class, cascade = CascadeType.ALL)private Address address;

With the adjustments implemented, you can now launch the application and confirm that all features operate seamlessly.

The source code is accessible at https://github.com/gudpick/jpa-demo/tree/json-managed-reference.

For additional guidance, video tutorials are provided below.


Isabella Clark

13 Blog posts

Comments