요구사항 분석
- 회원 관리 (User)
- 회원 가입
- 회원 정보 수정
- 로그인 / 로그아웃 기능
- 회원 조회
- 커뮤니티 (Board & Comment)
- 게시글 : 작성 / 수정 / 삭제 / 상세 조회
- 댓글 : 작성 / 수정 / 삭제
- 페이징 처리 : 게시글 목록 조회 시 성능 고려한 페이징 구현
- 검색 및 필터링 (Search)
- 게시글 제목의 키워드로 검색
- 작성자로 게시글 검색
- 프로젝트 시작 초기에는 간단히 회원/게시글/댓글로 CRUD만 구현해보려 했지만 구현하다보니 로그인/검색/페이징 기능이 추가되었다.
Repository 설계
User Repository
@Repository
@RequiredArgsConstructor
public class UserRepository {
private final EntityManager em;
public void save(User user) {
em.persist(user);
}
public Optional<User> findOne(Long userId) {
return Optional.ofNullable(em.find(User.class, userId));
}
public List<User> findAll() {
return em.createQuery("select u from User u", User.class).getResultList();
}
public Optional<User> findByEmail(String email) {
List<User> users = em.createQuery("select u from User u where u.email = :email", User.class)
.setParameter("email", email)
.getResultList();
return users.stream().findFirst();
}
}
Board Repository
@Repository
@RequiredArgsConstructor
public class BoardRepository {
public Optional<Board> findOne(Long boardId) {
return em.createQuery("select b from Board b join fetch b.user where b.id = :boardId", Board.class)
.setParameter("boardId", boardId)
.getResultStream().findFirst();
}
public List<Board> findAll() {
return em.createQuery("select b from Board b join fetch b.user order by b.createdAt desc", Board.class).getResultList();
}
public List<Board> findByUser(Long userId) {
return em.createQuery("select b from Board b join fetch b.user where b.user.id = :userId", Board.class)
.setParameter("userId", userId)
.getResultList();
}
public List<Board> findByTitle(String keyword) {
return em.createQuery("select b from Board b join fetch b.user where b.title like : keyword", Board.class)
.setParameter("keyword", "%"+keyword+"%")
.getResultList();
}
- 순수 JPA
- 스프링 데이터 JPA가 아닌 순수 JPA를 사용하면서
@RequiredArgsConstructor 생성자 주입을 사용해 EntityManager를 주입 받았다.
em.createQuery 메서드를 이용해 JPQL 구현
Optional<> 사용
findOne 이나 findByEmail 같이 단 건 조회하는 경우에 DB에 데이터가 없으면 Null이 반환되는데, Service 계층에서 null을 체크하여 NullPointerException을 방지하고, orElseThrow 로 예외 처리가 가능하도록 하였다.
- Fetch Join
- 단순 Join 대신 Fetch Join을 사용하여 연관된 엔티티를 한 번의 쿼리로 묶어서 가져오도록 리파지터리 코드 설계