We have learned in previous articles there are mainly three types of relationships between entities:
- One-To-One
- One-To-Many
- Many-To-Many
In this article we will continue our learning and learn about implementing Many-to-Many relationship.
Many-to-Many Relationship
A Many-to-Many relationship is a type of cardinality that refers to the relationship between two entities, say, A and B, where A may contain a parent instance for which there are many children in B and vice versa.
Let's take an example of Merchants and service provided by them. A merchant could provide a variety of services at the same time the same service could be provided by various other merchants.
The entity classes Merchant and Service are using Lombok to reduce boilerplate code, I suggest you to go through the Lombok article .
MerchantEntity.java
@Entity
@Table(name = "merchant")
@Getter @Setter @Builder
@NoArgsConstructor
@AllArgsConstructor
public class MerchantEntity {
@Id
@Column(name = "merchant_id")
private Integer merchantId;
@Column(name = "merchant_name")
private String merchantName;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "merchant_service_mapping",
joinColumns = @JoinColumn(name = "merchant_id", referencedColumnName = "merchant_id"),
inverseJoinColumns = @JoinColumn(name = "service_id", referencedColumnName = "service_id"))
private List<ServiceEntity> merchantService;
}
ServiceEntity.java
@Entity
@Table(name = "service")
@Getter @Setter @Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServiceEntity {
@Id
@Column(name = "service_id")
private Integer serviceId;
@Column(name = "service_name")
private String serviceName;
@ManyToMany(mappedBy = "merchantService", fetch = FetchType.EAGER)
private List<MerchantEntity> merchants;
}
The relationship in ER diagram is represented as follows:
The mapping is implemented in database using a mapping table which stores the primary key or identifying column from both the entities.
@JoinTable
is used to specify the name of mapping table and joining column details.
name = "merchant_service_mapping"
specifies the mapping table name where the primary keys or identifying keys will be stored.
joinColumns = @JoinColumn(name = "merchant_id", referencedColumnName = "merchant_id")
specifies that from merchant table merchant_id
column will be used to implement mapping.
inverseJoinColumns = @JoinColumn(name = "service_id", referencedColumnName = "service_id"
specifies that from service table service_id
column will be used to implement mapping.
referencedColumnName
is an optional property which is used to specify the name of the column referenced by this foreign key column.
mappedBy = "merchantService"
is used to specify the property in MerchantEntity
which is used to store the mapping details. This is required if the mapping is bideirectional.
Testing
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ManyToManyMappingTests {
@Autowired
private MerchantRepository merchantRepository;
@Autowired
private ServiceRepository serviceRepository;
private ServiceEntity serviceEntity1 = null;
private ServiceEntity serviceEntity2 = null;
private ServiceEntity serviceEntity3 = null;
private MerchantEntity merchantEntity1 = null;
private MerchantEntity merchantEntity2 = null;
@BeforeAll
public void setup() {
serviceEntity1 = ServiceEntity.builder()
.serviceId(1)
.serviceName("Plumbing")
.build();
serviceEntity2 = ServiceEntity.builder()
.serviceId(2)
.serviceName("Home Renovation")
.build();
serviceEntity3 = ServiceEntity.builder()
.serviceId(3)
.serviceName("Carpenting")
.build();
List<ServiceEntity> serviceList1 = new ArrayList<>();
serviceList1.add(serviceEntity1);
serviceList1.add(serviceEntity2);
List<ServiceEntity> serviceList2 = new ArrayList<>();
serviceList2.add(serviceEntity1);
serviceList2.add(serviceEntity3);
merchantEntity1 = MerchantEntity.builder()
.merchantId(1)
.merchantName("John's Builder")
.merchantService(serviceList1)
.build();
merchantEntity2 = MerchantEntity.builder()
.merchantId(2)
.merchantName("Jack Repairs")
.merchantService(serviceList2)
.build();
merchantEntity1 = merchantRepository.save(merchantEntity1);
merchantEntity2 = merchantRepository.save(merchantEntity2);
List<MerchantEntity> me1 = new ArrayList<>();
me1.add(merchantEntity1);
me1.add(merchantEntity2);
List<MerchantEntity> me2 = new ArrayList<>();
me2.add(merchantEntity1);
List<MerchantEntity> me3 = new ArrayList<>();
me3.add(merchantEntity2);
serviceEntity1.setMerchants(me1);
serviceEntity2.setMerchants(me2);
serviceEntity3.setMerchants(me3);
serviceRepository.save(serviceEntity1);
serviceRepository.save(serviceEntity2);
}
@Test
public void testManyToMany() {
MerchantEntity merchant1 = merchantRepository.findById(1).orElse(null);
assertNotNull(merchant1.getMerchantService());
assertEquals("Plumbing", merchant1.getMerchantService().get(0).getServiceName());
assertEquals("Home Renovation", merchant1.getMerchantService().get(1).getServiceName());
MerchantEntity merchant2 = merchantRepository.findById(2).orElse(null);
assertNotNull(merchant2.getMerchantService());
assertEquals("Plumbing", merchant2.getMerchantService().get(0).getServiceName());
assertEquals("Carpenting", merchant2.getMerchantService().get(1).getServiceName());
ServiceEntity serviceEntity1 = serviceRepository.findByServiceName("Plumbing");
assertNotNull(serviceEntity1.getMerchants());
assertEquals("John's Builder", serviceEntity1.getMerchants().get(0).getMerchantName());
assertEquals("Jack Repairs", serviceEntity1.getMerchants().get(1).getMerchantName());
ServiceEntity serviceEntity2 = serviceRepository.findByServiceName("Carpenting");
assertNotNull(serviceEntity2.getMerchants());
assertEquals("Jack Repairs", serviceEntity2.getMerchants().get(0).getMerchantName());
}
}
On running this test case, we will find all these test cases runs successfully and verifies that our implementation is correct.
Summary
In this article, we learnt about implementation of many-to-many mapping relationship in Spring Data JPA. And also we tested our implementation using JUnit test case.
You can find the source code of this post @ Github