The Best Fluffy Pancakes recipe you will fall in love with. Full of tips and tricks to help you make the best pancakes.

JPA Relationships in Spring Boot: A Complete Guide

In this article, we will learn how to implement different types of relationships between entities using Spring Boot JPA. We will walk through all three major relationship types:

  1. One-to-One
  2. One-to-Many / Many-to-One
  3. Many-to-Many

All examples use Lombok to reduce boilerplate code. If you’re not familiar with it, check out the Lombok documentation.

One-to-One Relationship

A one-to-one relationship means a record in one table is linked to exactly one record in another table.
Example: A Person entity has one-to-one mapping with a Passport entity.

Let’s see the code example.

DB ER diagram for the relationship would look as below:

one-to-one-mapping ER diagram

PersonEntity.java

@Entity
@Table(name = "person")
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PersonEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "person_id")
    private Integer personId;

    @Column(name = "person_name")
    private String personName;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "passport_number", unique = true)
    private PassportEntity passport;
}

PassportEntity.java

@Entity
@Table(name = "passport")
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PassportEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "passport_id")
    private Integer passportId;

    @Column(name = "passport_number")
    private String passportNumber;

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

    @Column(name = "passport_type")
    private String passportType;

    @OneToOne(mappedBy = "passport")
    private PersonEntity person;
}

Key Annotations

  • @OneToOne establishes the one-to-one relation.
  • @JoinColumn sets the foreign key and enforces uniqueness.
  • mappedBy indicates the inverse side of the relationship.
  • cascade = CascadeType.ALL propagates all operations.

One-to-Many Relationship

A one-to-many relationship is a type of cardinality that refers to the relationship between two entities A and B in which an element of A may be linked to many elements of B, but a member of B is linked to only one element of A.

Example: A person can have multiple credit cards and at the same time a credit card is linked to one person.

DB ER diagram for the relationship would look as below:

image.png

Person.java

@Entity
@Table(name = "person")
@Getter @Setter @Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {

	@Id
	@Column(name = "person_id")
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer personId;
	
	@Column(name = "person_name")
	private String personName;	
	
	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinTable(name = "person_card_mapping",
				joinColumns = @JoinColumn(name = "person_id"),
				inverseJoinColumns = @JoinColumn(name = "card_id"))
	private List<Card> cards;
	
}

Card.java

@Entity
@Table(name = "card")
@Getter @Setter @Builder
@NoArgsConstructor
@AllArgsConstructor
public class Card {

	@Id
	@Column(name = "card_id")
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer cardId;
	
	@Column(name = "card_no")
	private String cardNo;
	
	@Column(name = "cardholder_name")
	private String cardholderName;
	
	@Column(name = "expiry_month")
	private String expiryMonth;
	
	@Column(name = "expiry_year")
	private String expiryYear;	
}

Key Annotations

  • @OneToMany on Person makes it the owner of multiple Card instances.
  • @JoinTable creates an intermediate mapping table (person_card_mapping).

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.

Example: Let’s take an example of a Merchant 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.

DB ER diagram for the relationship would look as below:

image.png

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;
	
}

Key Annotations

  • @ManyToMany on both sides defines bidirectional many-to-many.
  • @JoinTable establishes the mapping table with foreign keys from both entities.
  • referencedColumnName ensures explicit column mapping.

Testing

All the relationships can be tested with simple JUnit code.

One-to-One Testing

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EntityMappingApplicationTests {


	@Autowired
	private PersonRepository personRepo;
	
	@Autowired
	private PassportRepository passportRepository;	

	private PersonEntity person = null;
	private PassportEntity passport = null;
	
	private Integer personId;
	
	@BeforeAll
    public void setup() {
		passport = PassportEntity.builder()
				.passportNumber("A1234567")
				.country("India")
				.passportType("Diplomatic")
				.build();

		person =  PersonEntity.builder()
				.personName("John Doe")
				.passport(passport)
				.cards(cards)
				.build();

		person = personRepo.save(person);
		personId = person.getPersonId();
    }

	@Test
	public void testOneToOne() {

		PersonEntity person  = personRepo.findById(personId).orElse(null);

		assertNotNull(person.getPassport());
		assertEquals("A1234567", person.getPassport().getPassportNumber());
		
		PassportEntity passport = passportRepository.findByPassportNumber("A1234567");
		
		assertNotNull(passport.getPerson());
		assertEquals("John Doe", passport.getPerson().getPersonName());

	}
}

One-to-Many Testing

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class OneToManyMappingTests {

	@Autowired
	private PersonRepository personRepo;

	private Person person = null;
	private Card card1 = null;
	private Card card2 = null;

	private Integer personId;
	
	@BeforeAll
    public void setup() {

		card1 = Card.builder()
				.cardholderName("John Doe")
				.cardNo("4578963214502354")
				.expiryMonth("12")
				.expiryYear("2022")
				.build();

		card2 = Card.builder()
				.cardholderName("John Doe")
				.cardNo("4111111111111111")
				.expiryMonth("02")
				.expiryYear("2025")
				.build();

		List<Card> cards = new ArrayList<>();
		cards.add(card1);
		cards.add(card2);
		
		person =  Person.builder()
				.personName("John Doe")
				.cards(cards)
				.build();

		person = personRepo.save(person);
		personId = person.getPersonId();
    }

	
	@Test
	public void testOneToMany() {

		Person person  = personRepo.findById(personId).orElse(null);

		assertNotNull(person.getCards());
		assertEquals("4578963214502354", person.getCards().get(0).getCardNo());
		assertEquals("4111111111111111", person.getCards().get(1).getCardNo());

	}

}

Many-to-Many 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());
	}
}

Summary

Relationship TypeCardinalityJoin table needed
One-to-OneA ↔ B (1:1)No
One-to-ManyA → B (1:N)Optional
Many-to-ManyA ↔ B (N:M)Yes
  1. Use cascade to control lifecycle propagation.
  2. Use mappedBy on the inverse side to avoid duplicate mapping.
  3. Use @JoinTable only when needed for relationship tables.

Source Code

You can find the full working examples and test cases on GitHub: Entity Mapping

Leave a Reply

Your email address will not be published. Required fields are marked *