The Best Fluffy Pancakes recipe you will fall in love with. Full of tips and tricks to help you make the best pancakes.
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:
- One-to-One
- One-to-Many / Many-to-One
- 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:

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:

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
onPerson
makes it the owner of multipleCard
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:

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 Type | Cardinality | Join table needed |
One-to-One | A ↔ B (1:1) | No |
One-to-Many | A → B (1:N) | Optional |
Many-to-Many | A ↔ B (N:M) | Yes |
- Use
cascade
to control lifecycle propagation. - Use
mappedBy
on the inverse side to avoid duplicate mapping. - Use
@JoinTable
only when needed for relationship tables.
Source Code
You can find the full working examples and test cases on GitHub: Entity Mapping