본문 바로가기
ToyProject

스프링 게시판 만들기 - 2 (도메인 생성 , Member 테스트)

by 완두완두콩 2022. 1. 12.

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

 

스프링 게시판 만들기 - 3 (화면 생성 / HTML , CSS with BootStrap 사용법)

파일 경로 ※ static 아래 index.html(파일명 동일) 을 두는 경우 , spring boot 에서는 자동으로 welcomePage 로 인식해서 localhost:8080 을 띄우는 경우 , 첫 화면으로 생성된다. static 하위에 정적 리소스들..

dodokong.tistory.com

 

댓글