Member 서비스, 레포지토리
MemberService.java
package project.post.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import project.post.domain.Member;
import project.post.repository.MemberRepository;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
/**
* 회원 가입
*/
@Transactional
public Long join(Member member){
validMember(member);
memberRepository.save(member);
return member.getId();
}
/**
* 회원 중복 조회
*/
private void validMember(Member member){
int result = memberRepository.findByEmail(member.getEmail());
if(result > 0){
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers(){
return memberRepository.findAll();
}
/**
* 특정 회원 조회
*/
public Member findOne(Long memberId){
return memberRepository.findOne(memberId);
}
}
- @RequiredArgsConstructor : 초기화 되지않은 final 필드나, @NotNull이 붙은 필드에 대해 생성자를 생성해 줌
- 새로운 필드를 추가할 때 다시 생성자를 만들어야 하는 번거로움을 없애줌(@Autowired를 사용하지 않고 의존성 주입)
- @Transactional : 트랜잭션 처리를 위한 어노테이션
- 클래스 단위 혹은 메서드 단위에 선언
- 클래스에 선언 시 해당 클래스에 속하는 public 메서드에 공통적으로 적용
- 클래스에 선언된 트랜잭션의 우선순위가 가장 높고, 인터페이스에 선언된 트랜잭션의 우선순위가 가장 낮음
- 메서드에 선언 시 해당 메서드만 적용
- readOnly = true 속성을 설정함으로써 클래스 전체에 읽기 전용 모드로 설정
- 읽기 전용 모드로 설정하는 이유는 영속성 컨텍스트에 flush를 하지 않으므로 약간의 성능 최적화
- @PostConstruct 같이 초기화 코드에 선언 시 트랜잭션 미적용
MemberRepository.java
package project.post.repository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import project.post.domain.Member;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member){
em.persist(member);
}
public int findByEmail(String memberEmail){
return em.createQuery("select m from Member m"
+" where m.email = :email", Member.class)
.setParameter("email",memberEmail)
.getResultList()
.size();
}
public List<Member> findAll(){
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Member findOne(Long memberId){
return em.find(Member.class, memberId);
}
}
- @Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
Post 서비스, 레포지토리
PostDto.java
package project.post.dto;
import lombok.Getter;
import lombok.Setter;
import project.post.domain.PostStatus;
import java.time.LocalDateTime;
@Getter
@Setter
public class PostDto {
String title;
String content;
LocalDateTime postDate;
public PostDto(String title, String content, LocalDateTime postDate) {
this.title = title;
this.content = content;
this.postDate= postDate;
}
}
PostService.java
package project.post.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import project.post.domain.Member;
import project.post.domain.Post;
import project.post.domain.PostStatus;
import project.post.dto.PostDto;
import project.post.repository.MemberRepository;
import project.post.repository.PostRepository;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {
private final MemberRepository memberRepository;
private final PostRepository postRepository;
//private final PostStatus postStatus;
/**
* 게시글 등록
*/
@Transactional
public Long savePost(Long memberId, PostDto postDto){
Member member = memberRepository.findOne(memberId);
Post post = new Post();
post.setTitle(postDto.getTitle());
post.setContent(postDto.getContent());
post.setStatus(PostStatus.CREATE);
post.setPostDate(postDto.getPostDate());
post.setMember(member);
postRepository.savePost(post);
return post.getId();
}
/**
* 게시글 조회
*/
public List<Post> findPost(){
return postRepository.findAll();
}
/**
* 제목, 내용으로 게시글 조회
*/
public List<Post> findPostByCondition(String title, String content){
return postRepository.findAllByCriteria(title, content);
}
public Post findPostById(Long postId){
return postRepository.findOne(postId);
}
/**
* 게시글 수정
*/
@Transactional
public void updatePost(Long postId, PostDto postDto){
Post findPost = postRepository.findOne(postId);
findPost.update(postDto.getTitle(), postDto.getContent(), PostStatus.MODIFY, LocalDateTime.now());
}
/**
* 게시글 삭제
*/
@Transactional
public void deletePost(Long postId){
Post findPost = postRepository.findOne(postId);
findPost.update(findPost.getTitle(), findPost.getContent(), PostStatus.DELETE, LocalDateTime.now());
}
}
- 게시글 수정, 삭제의 경우 status의 값만 변경할 예정이므로 데이터 수정으로 진행
- 변경 감지 기능 사용
- Repository에서 직접 update를 진행하는 것이 아닌, 엔티티 객체를 수정함으로써 commit 시점에 초기 저장된 엔티티(스냅샷)와 비교하여 변경된 내용이 있으면 자동으로 update 쿼리를 보내도록 구현
- 트랜잭션 내에서 조회 후 변경하면 commit 시점에 변경 감지 발생
- merge를 사용하는 경우, 모든 속성이 변경되기 때문에 파라미터로 전달되지 않은 필드의 값은 null로 변경되어 사이드 이펙트 발생 가능
- updatePost에서 직접 데이터를 set하지 않고 엔티티 내의 메서드에서 진행하는 이유는 추후 추적이 용이하도록 하기 위함(담당자 변경, 유지보수 등)
PostRepository.java
package project.post.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import project.post.domain.Post;
import project.post.domain.PostStatus;
import java.util.ArrayList;
import java.util.List;
@Repository
@AllArgsConstructor
public class PostRepository {
private EntityManager em;
public void savePost(Post post){
em.persist(post);
}
public List<Post> findAll(){
return em.createQuery("select p from Post p where p.status <> :postStatus", Post.class)
.setParameter("postStatus", PostStatus.DELETE)
.getResultList();
}
public Post findOne(Long postId){
return em.find(Post.class, postId);
}
public List<Post> findAllByCriteria(String title, String content){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Post> cq = cb.createQuery(Post.class);
Root<Post> p = cq.from(Post.class);
List<Predicate> criteria = new ArrayList<>();
// 제목 검색
if(StringUtils.hasText(title)){
Predicate findTitle = cb.like(p.<String>get("title"),"%" + title + "%");
criteria.add(findTitle);
}
// 내용 검색
if(StringUtils.hasText(content)){
Predicate findContent = cb.like(p.<String>get("content"),"%" + content + "%");
criteria.add(findContent);
}
Predicate basicCondition = cb.notEqual(p.get("status"), "DELETE");
criteria.add(basicCondition);
cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
TypedQuery<Post> query = em.createQuery(cq).setMaxResults(1000);
return query.getResultList();
}
}
- @AllArgsConstructor : 모든 필드 값을 파라미터로 받는 생성자를 생성해 줌
- findAll 메서드와 findAllByCriteria 메서드의 경우 status <> "DELETE"로 설정하여 삭제된 게시글은 조회되지 않도록 함
전체 코드
to_post/src/main/java/project/post at main · pparkcoder/to_post
[Side Project] SpringBoot + JPA를 활용한 게시판 만들기. Contribute to pparkcoder/to_post development by creating an account on GitHub.
github.com
'개발진행목록 > 게시판 서비스' 카테고리의 다른 글
[JPA로 게시판 만들기] API 개발 (0) | 2025.02.05 |
---|---|
[JPA로 게시판 만들기] 테스트 코드 작성 (0) | 2025.01.23 |
[JPA로 게시판 만들기] TDD, BDD 란 (1) | 2025.01.23 |
[JPA로 게시판 만들기] 설계 및 엔티티 생성 (0) | 2025.01.16 |
[JPA로 게시판 만들기] 프로젝트 환경 설정 및 DB 연동 (1) | 2025.01.16 |