본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.companyname.projectname
│ │ │ ├── post
│ │ │ │ ├── dto
│ │ │ │ │ ├── PostCreateRequestDto.java
│ │ │ │ │ ├── PostUpdateRequestDto.java
│ │ │ │ │ ├── PostDetailResponseDto.java
│ │ │ │ │ └── PostListResponseDto.java
│ │ │ │ ├── model
│ │ │ │ │ └── Post.java
│ │ │ │ ├── repository // 패키지 추가
│ │ │ │ │ └── PostRepository.java // 인터페이스 추가
│ │ │ │ └── service // 패키지 추가
│ │ │ │ ├── PostReadService.java // 인터페이스 추가
│ │ │ │ ├── PostReadServiceImpl.java // 클래스 추가
│ │ │ │ ├── PostWriteService.java // 인터페이스 추가
│ │ │ │ └── PostWriteServiceImpl.java // 클래스 추가
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
package com.companyname.projectname.post.repository;
import com.companyname.projectname.post.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// 1. 이름 기반 메서드 (쿼리 메서드)
// 기본 메서드
List<Post> findByTitle(String title);
List<Post> findByContent(String content);
List<Post> findByTitleContaining(String title);
List<Post> findByContentContaining(String content);
List<Post> findByTitleContainingIgnoreCase(String title);
List<Post> findByContentContainingIgnoreCase(String content);
// 복합 메서드
List<Post> findByTitleAndContent(String title, String content);
List<Post> findByTitleContainingOrContentContaining(String title, String content);
List<Post> findByTitleContainingAndContentContaining(String title, String content);
List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(String title, String content);
List<Post> findByTitleContainingIgnoreCaseAndContentContainingIgnoreCase(String title, String content);
// 날짜 기반 메서드 (생성 일자 기준)
List<Post> findByCreatedDateAfter(LocalDateTime date);
List<Post> findByCreatedDateBefore(LocalDateTime date);
List<Post> findByCreatedDateBetween(LocalDateTime sDate, LocalDateTime eDate);
// 날짜 기반 메서드 (수정 일자 기준)
List<Post> findByUpdatedDateAfter(LocalDateTime date);
List<Post> findByUpdatedDateBefore(LocalDateTime date);
List<Post> findByUpdatedDateBetween(LocalDateTime sDate, LocalDateTime eDate);
}
JPA
는 Entity
클래스의 필드명을 기준으로, 메서드명을 통해 JPA 원시 쿼리를 자동으로 생성확장성과 유지보수성을 고려하여
Interface
로 구현하였으며,
@Transactional
조회 성능 최적화를 위해 읽기, 쓰기 작업을 분리
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostListResponseDto;
import java.time.LocalDateTime;
import java.util.List;
public interface PostReadService {
PostDetailResponseDto findById(Long id);
List<PostListResponseDto> findAllLists();
List<PostDetailResponseDto> findAllDetails();
// 예시, 필요한 경우 작성
List<PostListResponseDto> findByTitle(String title);
List<PostListResponseDto> findByContent(String content);
List<PostListResponseDto> findByTitleContaining(String title);
List<PostListResponseDto> findByContentContaining(String content);
List<PostListResponseDto> findByTitleContainingIgnoreCase(String title);
List<PostListResponseDto> findByContentContainingIgnoreCase(String content);
List<PostListResponseDto> findByTitleAndContent(String title, String content);
List<PostListResponseDto> findByTitleContainingOrContentContaining(String title, String content);
List<PostListResponseDto> findByTitleContainingAndContentContaining(String title, String content);
List<PostListResponseDto> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(String title, String content);
List<PostListResponseDto> findByTitleContainingIgnoreCaseAndContentContainingIgnoreCase(String title, String content);
List<PostListResponseDto> findByCreatedDateAfter(LocalDateTime date);
List<PostListResponseDto> findByCreatedDateBefore(LocalDateTime date);
List<PostListResponseDto> findByCreatedDateBetween(LocalDateTime sDate, LocalDateTime eDate);
List<PostListResponseDto> findByUpdatedDateAfter(LocalDateTime date);
List<PostListResponseDto> findByUpdatedDateBefore(LocalDateTime date);
List<PostListResponseDto> findByUpdatedDateBetween(LocalDateTime sDate, LocalDateTime eDate);
}
Interface
- 클래스 간 공통된 메서드의 규격을 정의
- 다형성을 활용하여 유연하고 확장 가능한 코드 설계 가능
- 메서드 선언부만 존재하며, 구현부는 구현 클래스에서 정의
- 다중 상속을 지원하여 다양한 인터페이스를 한 클래스에서 구현 가능
Interface
주의할 점
- 구현 클래스에서 반드시 메서드를
@Override
해야 함- 다중 상속 시 중복된 메서드가 존재하는 경우 충돌에 주의
- 구현해야할 기능의 정의는 결정되었으나, 로직은 결정되지 않은 경우 사용
- 필요 이상으로 많은 메서드를 정의하면 구현 클래스의 복잡도를 증가시킬 수 있음
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostListResponseDto;
import com.companyname.projectname.post.model.Post;
import com.companyname.projectname.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostReadServiceImpl implements PostReadService {
private final PostRepository postRepository;
@Override
public PostDetailResponseDto findById(Long id) {
Post entity = postRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("Post not found with id: " + id));
return new PostDetailResponseDto(entity);
}
@Override
public List<PostListResponseDto> findAllLists() {
return postRepository.findAll().stream().map(PostListResponseDto::new).toList();
}
@Override
public List<PostDetailResponseDto> findAllDetails() {
return postRepository.findAll().stream().map(PostDetailResponseDto::new).toList();
}
@Override
public List<PostListResponseDto> findByTitle(String title) {
return postRepository.findByTitle(title).stream().map(PostListResponseDto::new).toList();
}
@Override
public List<PostListResponseDto> findByContent(String content) {
return postRepository.findByContent(content).stream().map(PostListResponseDto::new).toList();
}
// ... 중략 ...
}
@Transactional(readOnly = true)
Spring
제공 어노테이션, 조회 전용 메서드에 설정하여 성능 최적화를 도모
쓰기 작업을 막고 읽기 전용으로 동작, 일부 데이터베이스에서 캐시를 활용하도록 도움- 트랜잭션 범위를 설정하고, 데이터베이스 작업의 일관성과 원자성을 보장
- 데이터 수정/삭제/저장 작업에서 예외 발생 시, 롤백을 자동으로 처리
- 클래스 레벨에 선언하면 해당 클래스의 모든 메서드에 트랜잭션 속성 적용
- 메서드 레벨에 선언하면 해당 메서드에만 트랜잭션 속성 적용
- 클래스 레벨과 메서드 레벨에 모두 선언하면
메서드 레벨 설정이 클래스 레벨 설정을 재정의
@RequiredArgsConstructor
- 필드 주입 대신 생성자 주입으로 PostRepository 자동으로 의존성 주입
- Spring DI(Dependency Injection) 컨테이너에서 안전하게 의존성 관리
- 선언된
final
필드는 생성자에서 자동으로 초기화IllegalArgumentException
- 데이터가 없을 경우, orElseThrow()를 사용하여 명시적으로 예외를 던짐
- 추후 별도의 예외 클래스를 생성하여 더 구체적인 예외 처리가 가능하도록 변경
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.dto.PostCreateRequestDto;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostUpdateRequestDto;
public interface PostWriteService {
PostDetailResponseDto create(PostCreateRequestDto requestDto);
PostDetailResponseDto update(PostUpdateRequestDto requestDto);
void delete(Long id);
}
Interface
- 클래스 간 공통된 메서드의 규격을 정의
- 다형성을 활용하여 유연하고 확장 가능한 코드 설계 가능
- 메서드 선언부만 존재하며, 구현부는 구현 클래스에서 정의
- 다중 상속을 지원하여 다양한 인터페이스를 한 클래스에서 구현 가능
Interface
주의할 점
- 구현 클래스에서 반드시 메서드를
@Override
해야 함- 다중 상속 시 중복된 메서드가 존재하는 경우 충돌에 주의
- 구현해야할 기능의 정의는 결정되었으나, 로직은 결정되지 않은 경우 사용
- 필요 이상으로 많은 메서드를 정의하면 구현 클래스의 복잡도를 증가시킬 수 있음
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.dto.PostCreateRequestDto;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostUpdateRequestDto;
import com.companyname.projectname.post.model.Post;
import com.companyname.projectname.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class PostWriteServiceImpl implements PostWriteService {
private final PostRepository postRepository;
@Override
public PostDetailResponseDto create(PostCreateRequestDto requestDto) {
return new PostDetailResponseDto(postRepository.save(requestDto.toEntity()));
}
@Override
public PostDetailResponseDto update(PostUpdateRequestDto requestDto) {
Post entity = postRepository.findById(requestDto.getId()).orElseThrow(
() -> new IllegalArgumentException("Post not found with id: " + requestDto.getId()));
return new PostDetailResponseDto(postRepository.save(entity.update(requestDto)));
}
@Override
public void delete(Long id) {
postRepository.deleteById(id);
}
}
@Transactional
Spring
제공 어노테이션- 트랜잭션 범위를 설정하고, 데이터베이스 작업의 일관성과 원자성을 보장
- 데이터 수정/삭제/저장 작업에서 예외 발생 시, 롤백을 자동으로 처리
- 클래스 레벨에 선언하면 해당 클래스의 모든 메서드에 트랜잭션 속성 적용
- 메서드 레벨에 선언하면 해당 메서드에만 트랜잭션 속성 적용
- 클래스 레벨과 메서드 레벨에 모두 선언하면
메서드 레벨 설정이 클래스 레벨 설정을 재정의
@RequiredArgsConstructor
- 필드 주입 대신 생성자 주입으로 PostRepository 자동으로 의존성 주입
- Spring DI(Dependency Injection) 컨테이너에서 안전하게 의존성 관리
- 선언된
final
필드는 생성자에서 자동으로 초기화IllegalArgumentException
- 데이터가 없을 경우, orElseThrow()를 사용하여 명시적으로 예외를 던짐
- 추후 별도의 예외 클래스를 생성하여 더 구체적인 예외 처리가 가능하도록 변경
본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.