Many-to-Many Relationship Spring Data Jpa

Many-to-Many Relationship Spring Data Jpa

ยท

3 min read

We have learned in previous articles there are mainly three types of relationships between entities:

  1. One-To-One
  2. One-To-Many
  3. 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:

image.png

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

ย