Article:
In Spring Boot applications, separating your data access and business logic layers is a best practice that leads to clean, maintainable code. In this article, we will create a JPA repository and service layer for an entity class CollegeCourse
that manages the many-to-many relationship between College
and Course
.
🧱 Step 1: Define the CollegeCourse
Entity
java
import javax.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
public class CollegeCourse {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.EAGER, optional = true)
@JoinColumn(name = "collegeId", referencedColumnName = "id", nullable = true)
@OnDelete(action = OnDeleteAction.CASCADE)
private College college;
@ManyToOne(fetch = FetchType.EAGER, optional = true)
@JoinColumn(name = "courseId", referencedColumnName = "id", nullable = true)
@OnDelete(action = OnDeleteAction.CASCADE)
private Course course;
// Getters and Setters
}
This entity links a College
and a Course
, allowing you to model a many-to-many relationship explicitly using a join table (CollegeCourse
).
📁 Step 2: Create the CollegeCourseRepository
java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CollegeCourseRepository extends JpaRepository<CollegeCourse, Long> {
// Add custom query methods if needed
}
The repository layer provides built-in methods for CRUD operations. It extends JpaRepository
, which saves you from writing boilerplate DAO logic.
🔧 Step 3: Define the CollegeCourseService
Interface
java
import java.util.List;
public interface CollegeCourseService {
CollegeCourse saveCollegeCourse(CollegeCourse collegeCourse);
CollegeCourse getCollegeCourseById(Long id);
List<CollegeCourse> getAllCollegeCourses();
void deleteCollegeCourse(Long id);
}
This interface abstracts your business logic operations and makes the service layer testable and easier to mock.
🚀 Step 4: Implement the CollegeCourseServiceImpl
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class CollegeCourseServiceImpl implements CollegeCourseService {
private final CollegeCourseRepository collegeCourseRepository;
@Autowired
public CollegeCourseServiceImpl(CollegeCourseRepository collegeCourseRepository) {
this.collegeCourseRepository = collegeCourseRepository;
}
@Override
public CollegeCourse saveCollegeCourse(CollegeCourse collegeCourse) {
return collegeCourseRepository.save(collegeCourse);
}
@Override
public CollegeCourse getCollegeCourseById(Long id) {
Optional<CollegeCourse> optional = collegeCourseRepository.findById(id);
return optional.orElseThrow(() ->
new RuntimeException("CollegeCourse not found with id: " + id));
}
@Override
public List<CollegeCourse> getAllCollegeCourses() {
return collegeCourseRepository.findAll();
}
@Override
public void deleteCollegeCourse(Long id) {
collegeCourseRepository.deleteById(id);
}
}
This class provides actual implementations for all methods defined in the interface, delegating the persistence logic to CollegeCourseRepository
.
✅ Summary
With this structure, you achieve a clean separation between the entity, repository, and service layers. This approach:
- Keeps your code modular
- Promotes good testing practices
- Makes your logic reusable and maintainable
This pattern is widely used in enterprise-level Spring Boot applications.