DB 생성 코드
Member
package toyproject.board.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "member")
@Setter @Getter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@NotBlank(message = "아이디를 입력해주세요.")
@Pattern(regexp = "^[a-zA-Z0-9]{3,12}$", message = "아이디를 3~12자로 입력해주세요. [특수문자 X]")
private String username;
@NotBlank(message = "비밀번호를 입력해주세요.")
@Pattern(regexp = "^[a-zA-Z0-9]{3,12}$", message = "비밀번호를 3~12자로 입력해주세요.")
private String password;
private String email;
@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
private List<Board> board = new ArrayList<>();
@Builder
public Member(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
protected Member() {}
}
@Table -> 테이블 명은 member ,Id 는 PK 로 두고 AUTOINCREMENT 로 설정한다.
@NotBlank 와 @Pattern 은 validation 을 위한 어노테이션 -> 추후 구현
1:N 관계 이므로 @OneToMany 어노테이션.
@Builder 는 @Setter 를 두지 않기 위한 어노테이션. Setter 를 이용해서 값을 설정하는 것은 개발자의 의도와 다르게 값이 변경될 가능성이 있기에 사용한다. 기본 생성자는 protected 로 막는다.
Board
package toyproject.board.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "board")
@Getter @Setter
public class Board {
@Id @GeneratedValue
@Column(name = "board_id")
private Long id;
private String title;
private String content;
private LocalDateTime createdDate;
private Long createdBy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
Board 테이블은 기본적인 매핑만 해주고 추후 수정. (연관관계 매핑 필요)
우선 Member 로직 먼저 구현 후 , 테스트 할 예정이다.
Member 관련 코드
MemberRepository
package toyproject.board.domain.Repository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import toyproject.board.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void saveMember(Member member){
em.persist(member);
}
public Member findOne(Long id){
return em.find(Member.class, id);
}
public List<Member> findAll(){
return em.createQuery("select m from Member m")
.getResultList();
}
public List<Member> findByName(String username) {
return em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", username)
.getResultList();
}
}
JPA 를 이용하여 멤버 저장하는 쿼리와 하나의 id 찾기 , 전체 찾기 , 일치하는 이름(아이디) 찾는 메소드 각각 작성.
MemberService
package jpashop.jpashopmin.domain.Service;
import jpashop.jpashopmin.domain.Member;
import jpashop.jpashopmin.domain.Repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
@Transactional
public Long join(Member member){
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if(!findMembers.isEmpty()){
throw new IllegalStateException("이미 존재하는 이름입니다.");
}
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
@Transactional 에서 옵션을 read only 로 주면 쿼리 성능 향상.
다만 기본적인 조회가 아닌 영속성 컨텍스트에 값이 들어가야 하는 경우 @Transactional 어노테이션을 해당 메소드에 작성한다.
@Transactional read only 를 클래스 레벨에 적용하면 모든 메소드에 read only 옵션이 적용된다. -> 필요한 메소드만 @Transactional 사용한다.
MemberServiceTest
package toyproject.board.domain.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import toyproject.board.domain.Member;
import toyproject.board.domain.Repository.MemberRepository;
import toyproject.board.domain.Service.MemberService;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
@Commit
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
void 회원_가입(){
//given
Member member = Member.builder()
.username("mins")
.password("1234")
.email("email")
.build();
//when
Long joinId = memberService.join(member);
//then
Assertions.assertEquals(member, memberRepository.findOne(joinId));
}
@Test
void 중복_회원_가입 (){
//given
Member member = Member.builder()
.username("min")
.password("1234")
.email("email")
.build();
Member member2 = Member.builder()
.username("min")
.password("1234")
.email("email")
.build();
//when
memberService.join(member);
//then
assertThrows(IllegalStateException.class , () -> memberService.join(member2));
}
}
void 회원_가입 : 회원 가입이 정상적으로 이루어지는지 확인하는 테스트
다음과 같이 DB 에 잘 저장됨을 확인 가능하다.
Setter 를 두지 않고 @Builder 를 사용해서 값을 저장한다.
void 중복_회원_가입 : Junit4 와 다르게 Junit5는 exception 오류 검출 로직 변경 -> //then 확인
※ 주의 : 테스트 케이스에서는 항상 데이터가 롤백되기 때문에 @Commit 어노테이션을 적어주지 않으면 값이 저장되지 않는다.
※ 참고
테스트 환경에서 DB 저장을 원하는 경우
application.properties 또는 yml 파일을 test - resources - application.properties 파일 생성
다음과 같은 코드 작성 (H2 DB 기준 , 처음 h2 설정은 본인 DB 에 맞춰서 진행)
JPA ddl-auto 는 create 로 설정 -> 운영 단계에서 변경
실제 DB 가 필요 없다 하면 모두 주석 처리 하게 되면 내부 메모리에 데이터를 저장했다가 자동 롤백한다.
(H2 꺼진 상태로 가능 & 테스트 성공 실패 유무 확인 가능 )
# H2 ??
spring.datasource.url=jdbc:h2:tcp://localhost/~/board
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
# JPA ??
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.format_sql=true
# Log ??
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type=trace
다음편!
https://dodokong.tistory.com/21?category=1249752
'ToyProject' 카테고리의 다른 글
스프링 게시판 만들기 - 5 (2) ( 로그인 처리 feat.스프링 시큐리티) (0) | 2022.01.22 |
---|---|
스프링 게시판 만들기 - 5 (1) ( 로그인 처리 feat.스프링 시큐리티) (2) | 2022.01.21 |
스프링 게시판 만들기 - 4 (회원 가입 & Controller 만들기) (0) | 2022.01.15 |
스프링 게시판 만들기 - 3 (화면 생성 / HTML , CSS with BootStrap 사용법) (0) | 2022.01.14 |
스프링 게시판 만들기 - 1 (초기 설정) (0) | 2022.01.11 |
댓글