자바 가이드 문서예시를 스프링부트 최신 버전에 맞추어 코드 리팩토링 및 성능 개선함
자바 가이드 원본보기 클릭
리팩토링 깃허브 레포 클릭
Selected below dependencies while creating spring boot project using spring initializr:
- Spring Web
- Thymeleaf
- Spring Data JPA
- MySQL Driver
- Spring Boot Devtools
- 📦main
- 📂java
- 📂com
- 📂walab
- 📂SpringBootCRUDTutorial
- 📂controller
- 📄StudentController.java
- 📂entity
- 📄Student.java
- 📂repository
- 📄StudentRepository.java
- 📂service
- 📄StudentService.java
- 📄SpringBootCrudTutorialApplication.java
- 📂resources
- 📂static
- 📂templates
- 📄create_student.html
- 📄edit_student.html
- 📄students.html
- 📄application.yml
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
public Student() {
}
public Student(String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
@Entity
@Table(name = "students")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
}
리팩토링된 코드에는 @Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor 어노테이션을 사용
- 코드 간결성이 눈에 띄게 향상되어, 소스 코드 읽기가 훨씬 쉬워짐.
- getter와 setter 메소드를 포함한 생성자들이 자동으로 생성되므로, 유지보수가 훨씬 용이함.
- 필드 변경 시 자동으로 관련 메소드가 업데이트되기 때문에, 코드 수정이 더 빠르고 간단함.
- Lombok 사용으로 인한 컴파일 시간의 미세한 증가는 있지만, 실행 시간 성능에는 영향 없음.
- 개발 효율성과 코드 관리성이 크게 개선됨.
public interface StudentRepository extends JpaRepository<Student, Long>{
}
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
@Repository 어노테이션의 유무: 두 번째 선언에는 @Repository 어노테이션이 명시적으로 포함되어 있음.
이 어노테이션은 스프링 컨테이너에게 해당 인터페이스가 데이터 액세스 계층의 컴포넌트임을 알려주는 역할을 함.
- 스프링의 예외 변환: @Repository 어노테이션은 스프링의 데이터 접근 예외를 스프링의 DataAccessException으로 자동 변환하는 추가적인 이점을 제공함. 이는 데이터베이스 작업 중 발생할 수 있는 예외 처리를 일관성 있게 관리할 수 있게 해줌.
- 컴포넌트 스캐닝: @Repository 어노테이션을 사용하면 스프링이 자동으로 컴포넌트 스캐닝을 통해 해당 인터페이스의 구현체를 빈으로 등록함. 이는 명시적으로 빈을 등록하는 과정을 생략할 수 있게 해줌.
- @Repository 어노테이션을 포함 => 데이터 액세스 계층의 컴포넌트를 명확히 식별하고, 예외 처리 및 빈의 자동 등록과 같은 장점이 있음.
public interface StudentService {
List<Student> getAllStudents();
Student saveStudent(Student student);
Student getStudentById(Long id);
Student updateStudent(Student student);
void deleteStudentById(Long id);
}
}
@Service
public class StudentServiceImpl implements StudentService{
private StudentRepository studentRepository;
public StudentServiceImpl(StudentRepository studentRepository) {
super();
this.studentRepository = studentRepository;
}
@Override
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
@Override
public Student saveStudent(Student student) {
return studentRepository.save(student);
}
@Override
public Student getStudentById(Long id) {
return studentRepository.findById(id).get();
}
@Override
public Student updateStudent(Student student) {
return studentRepository.save(student);
}
@Override
public void deleteStudentById(Long id) {
studentRepository.deleteById(id);
}
}
@RequiredArgsConstructor
@Service
public class StudentService {
private final StudentRepository studentRepository;
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
public Student getStudentById(Long id) {
return studentRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Student not found for id " + id));
}
public Student saveStudent(Student student) {
return studentRepository.save(student);
}
public Student updateStudent(Long id, Student studentDetails) {
return studentRepository.findById(id)
.map(existingStudent -> {
existingStudent.setFirstName(studentDetails.getFirstName());
existingStudent.setLastName(studentDetails.getLastName());
existingStudent.setEmail(studentDetails.getEmail());
return studentRepository.save(existingStudent);
})
.orElseThrow(() -> new RuntimeException("Student not found for id " + id));
}
public void deleteStudentById(Long id) {
studentRepository.findById(id).orElseThrow(() -> new RuntimeException("Student not found for id " + id));
studentRepository.deleteById(id);
}
}
- 생성자 주입 자동화: @RequiredArgsConstructor를 통해 코드 간결화 및 의존성 주입 간소화를 달성, 불변성 보장 및 순환 참조 방지!!!(중요)
- 예외 처리 향상: .orElseThrow() 사용으로, 학생 조회 및 삭제 시 명확한 예외 피드백 제공함.
- 업데이트 로직 최적화: 학생 정보 업데이트 시 변경 감지를 활용, 데이터베이스와의 불필요한 상호작용 감소시킴.
- 코드 및 프로젝트 간결성: Lombok 어노테이션과 서비스 구현 병합으로 코드 단순화 및 프로젝트 구조 단순화를 이룸, 가독성 및 유지보수성 향상 및 개발 속도 증가에 기여함.
- 성능 영향 최소화: 런타임 성능에 직접적 영향은 없지만, 개발 및 유지보수의 효율성 증가
@Controller
public class StudentController {
private StudentService studentService;
public StudentController(StudentService studentService) {
super();
this.studentService = studentService;
}
// handler method to handle list students and return mode and view
@GetMapping("/students")
public String listStudents(Model model) {
model.addAttribute("students", studentService.getAllStudents());
return "students";
}
@GetMapping("/students/new")
public String createStudentForm(Model model) {
// create student object to hold student form data
Student student = new Student();
model.addAttribute("student", student);
return "create_student";
}
@PostMapping("/students")
public String saveStudent(@ModelAttribute("student") Student student) {
studentService.saveStudent(student);
return "redirect:/students";
}
@GetMapping("/students/edit/{id}")
public String editStudentForm(@PathVariable Long id, Model model) {
model.addAttribute("student", studentService.getStudentById(id));
return "edit_student";
}
@PostMapping("/students/{id}")
public String updateStudent(@PathVariable Long id,
@ModelAttribute("student") Student student,
Model model) {
// get student from database by id
Student existingStudent = studentService.getStudentById(id);
existingStudent.setId(id);
existingStudent.setFirstName(student.getFirstName());
existingStudent.setLastName(student.getLastName());
existingStudent.setEmail(student.getEmail());
// save updated student object
studentService.updateStudent(existingStudent);
return "redirect:/students";
}
// handler method to handle delete student request
@GetMapping("/students/{id}")
public String deleteStudent(@PathVariable Long id) {
studentService.deleteStudentById(id);
return "redirect:/students";
}
}
@RequiredArgsConstructor
@Controller
public class StudentController {
private final StudentService studentService;
@GetMapping("/students")
public String listStudents(Model model) {
model.addAttribute("students", studentService.getAllStudents());
return "students";
}
@GetMapping("/students/new")
public String createStudentForm(Model model) {
model.addAttribute("student", new Student());
return "create_student";
}
@GetMapping("/students/edit/{id}")
public String editStudentForm(@PathVariable Long id, Model model) {
model.addAttribute("student", studentService.getStudentById(id));
return "edit_student";
}
@PostMapping("/students")
public String saveStudent(@ModelAttribute("student") Student student) {
studentService.saveStudent(student);
return "redirect:/students";
}
@PostMapping("/students/{id}")
public String updateStudent(@PathVariable Long id, @ModelAttribute("student") Student student, Model model) {
studentService.updateStudent(id, student);
return "redirect:/students";
}
@GetMapping("/students/{id}")
public String deleteStudent(@PathVariable Long id) {
studentService.deleteStudentById(id);
return "redirect:/students";
}
}
- 생성자 주입 자동화:
@RequiredArgsConstructor 어노테이션을 사용하여 생성자 주입이 자동화 => StudentService에 대한 의존성 주입이 간소화되고, 생성자 코드를 명시적으로 작성할 필요가 없어짐- 코드의 간결성 향상:
리팩토링 과정에서 중복 코드가 제거되어, 전체적인 코드의 양이 줄고 가독성이 향상. updateStudent 메서드에서 existingStudent 객체를 별도로 생성하고 설정하는 과정이 StudentService 내의 로직으로 이동- 일관된 데이터 처리(중요):
StudentService 내에서 데이터 처리 로직을 중앙집중화함 => 컨트롤러는 더욱 단순화되고 서비스 레이어가 비즈니스 로직을 처리하는 책임을 갖게 됨 => 이는 아키텍처의 분리 원칙을 더욱 잘 따르는 구조- 향상된 예외 처리:
getStudentById와 deleteStudentById 메소드에서 .orElseThrow()를 사용하여, 존재하지 않는 학생 ID에 대한 요청 시 명확한 예외 처리를 수행 => 이는 잠재적 오류를 식별 용이- 유지보수와 확장성:
코드가 더 간결하고 명확해짐 => 유지보수, 기능 확장 시 유리. @RequiredArgsConstructor 사용으로 인한 의존성 주입의 자동화는 새로운 의존성이 추가되거나 변경될 때 개발자가 수행해야 하는 작업을 줄여줌
직접적인 성능향상이 발생할 정도의 서비스가 아니지만 코드의 간결함과 예외처리, mvc패턴에서 각 계층의 역할에만 집중할 수 있게 리팩토링을 해보았습니다.