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.Next, we'll examine how Hibernate generates the tables.
In 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 JpaRepository<Address, 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 JpaRepository<Organization, Long> {}
Address Management Controller
@RestControllerpublic class AddressController { @Autowired private AddressRepository addressRepository; @GetMapping("/address/retrieve/all") public List<Address> 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 ResponseEntity<Object> createOrganization(@RequestBody Organization organization) { return organizationService.createOrganization(organization); } @DeleteMapping("/organization/delete/{id}") public ResponseEntity<Object> 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 List<Organization> getOrganizations() { return organizationRepository.findAll(); } @PutMapping("/organization/update/{id}") public ResponseEntity<Object> 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 ResponseEntity<Object> 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 ResponseEntity<Object> 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.