This blog is a continuation of Spring Boot JPA - association mapping
Today we will see one-to-many and many-to-one relationship mapping in spring boot.
One-to-many association
In Database, a one-to-many relationship occurs when one row in a table is mapped to many rows in another table. For example, a student can score marks in different subjects.
Let's create a student and mark table like the below one where one student can have many marks.
The mark table contains the student_id as the foreign key to the student table
One-To-Many annotation
The @OneToMany annotation defines the one-to-many relationship between two entities in Spring data.
In a bidirectional relationship, the parent entity contains the @OneToMany annotation and the child entity specifies the attributes mappedBy in the @OneToMany annotation.
Entity classes
we will update the Student class created in the previous blog and create a Mark entity class.
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "parentid", referencedColumnName = "id")
private Parent parent;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "studentid", referencedColumnName = "id")
private List < Mark > marks;
}
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Mark {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int mid;
private int studentid;
private int subjectid;
private int mark;
}
JPA Repository
The repository classes handle database operations(like insert, update, delete) by extending the JpaRepository and it must be annotated with @Repository so that our spring boot application detects it during component scanning.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.relational.entity.Student;
@Repository
public interface StudentRepository extends JpaRepository < Student, Integer > {
}
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.relational.entity.Mark;
public interface MarkRepository extends JpaRepository < Mark, Integer > {
}
Testing the Repository
Let's write some JUnit to test our repository.
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.relational.entity.Parent;
import com.example.relational.entity.Student;
import com.example.relational.repository.StudentRepository;
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class StudentRepositoryTest {
@Autowired
StudentRepository studentRepository;
@Test
public void getMarksForStudent() {
Student student = getStudent(2);
System.out.println("Student =" + student);
assertNotNull(student);
}
@Test
public void sumOfStudentMarks() {
Student student = getStudent(2);
int sum = student.getMarks().stream().mapToInt(mark -> mark.getMark()).sum();
assertNotNull(student);
assertEquals(181, sum);
}
private Student getStudent(int id) {
Optional < Student > studentOptional = studentRepository.findById(id);
return studentOptional.orElseGet(null);
}
}
we get the below logs on console when we run the above JUnit.
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_, student0_.parentid as parentid3_2_0_ from Student student0_ where student0_.id=?
Hibernate: select parent0_.id as id1_1_0_, parent0_.fatherName as fatherna2_1_0_, parent0_.motherName as motherna3_1_0_ from Parent parent0_ where parent0_.id=?
Hibernate: select marks0_.studentid as studenti3_0_0_, marks0_.id as id1_0_0_, marks0_.id as id1_0_1_, marks0_.mark as mark2_0_1_, marks0_.studentid as studenti3_0_1_, marks0_.subjectid as subjecti4_0_1_ from Mark marks0_ where marks0_.studentid=?
Student =Student(id=2, name=Mukesh, parent=Parent(pid=1, fatherName=Karthick, motherName=Divay), marks=[Mark(mid=2, studentid=2, subjectid=1, mark=45), Mark(mid=8, studentid=2, subjectid=3, mark=76), Mark(mid=12, studentid=2, subjectid=2, mark=60)])
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.relational.entity.Mark;
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MarkRepositoryTest {
@Autowired
MarkRepository markRepository;
@Test
public void getAllMArks() {
List < Mark > marks = markRepository.findAll();
System.out.println(marks);
assertEquals(4, marks.size());
}
@Test
public void insertMark() {
Mark mark = Mark.builder().studentid(5).studentid(1).mark(60).build();
Mark markInDB = markRepository.save(mark);
System.out.println(markInDB);
assertNotNull(markInDB);
}
}
we get the below logs on console when we run the above JUnit.
Many-to-one association
The many-to-one relationship exists when many rows in a table are linked to one row in another table.
In Spring Data, the @ManyToOne annotation is used to define the many-to-one relationship between entities.
For example, in a student grading system, each subject can have marks of all the students in the class where one subject has multiple student marks. This is a many-to-one relationship.
We have already created a mark table in the above one-to-many mapping, so let's create the subject table.
The mark table contains the subject_id (foreign key) pointing to the subject table primary key.
Entity class
Let's update the mark class we have created already as below
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Mark {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int mid;
private int studentid;
private int mark;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "subjectid", referencedColumnName = "id")
private Subject subject;
}
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Subject {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int sid;
private String title;
}
Create Repositories
we have added a method to fetch mark by subject title in the mark repository and created subject repostiroy.
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.relational.entity.Mark;
public interface MarkRepository extends JpaRepository < Mark, Integer > {
public List < Mark > findBySubjectTitle(String subjectName);
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.relational.entity.Subject;
@Repository
public interface SubjectRepository extends JpaRepository < Subject, Integer > {
}
Test Repository
Let write some Junit to test our mark repository.
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.relational.entity.Mark;
import com.example.relational.entity.Subject;
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MarkRepositoryTest {
@Autowired
MarkRepository markRepository;
@Autowired
SubjectRepository subjectRepository;
@Test
public void getAllMArks() {
List < Mark > marks = markRepository.findAll();
System.out.println(marks);
assertEquals(12, marks.size());
}
@Test
public void insertMark() {
Subject subject = subjectRepository.getById(1);
Mark mark = Mark.builder().subject(subject).studentid(5).mark(60).build();
Mark markInDB = markRepository.save(mark);
System.out.println(markInDB);
assertNotNull(markInDB);
}
@Test
public void getMarksBySubjectTitle() {
List < Mark > marks = markRepository.findBySubjectTitle("Java");
System.out.println(marks);
assertEquals(4, marks.size());
}
}
we can see below when we run our getMarksBySubjectTitle test method.