웹 애플리케이션 개발

J·2025년 1월 17일
0
post-thumbnail

웹 애플리케이션 개발 진행

*전체 소스코드

https://github.com/Code-Jihwan/hello-spring.git

<진행 순서>

스프링 프로잭트 생성

스프링 부트로 웹 서버 실행

회원 도메인 개발

웹 MVC 개발

DB 연동 - JDBC, JPA, 스프링 데이터 JPA

테스트 케이스 작성


프로잭트 성성

  • openjdk version "17.0.13"

  • IntelliJ 사용

  • 스프링 프로젝트 생성
    https://start.spring.io

  • 프로젝트 선택
    Project: Gradle - Groovy Project
    Spring Boot: 3.4.1
    Language: Java
    Packaging: Jar
    Java: 17

  • Project Metadata
    groupId: hello
    artifactId: hello-spring
    Dependencies: Spring Web, Thymeleaf
    (Thymeleaf : html 템플릿 엔진)


이렇게 설정하고 spring initializr에서 GENERATE 해주면, 스프링 부트 기반으로 스프링 프로잭트를 알아서 생성해준다.

이후 IntelliJ에서 파일 다운 받아서 작업 시작!

실행 후 localhost:8080 접속해서 서버 정상 동작 확인

프로잭트 환경 설정 완료.

main 메소드 실행하면 스프링 부트 애플리케이션 실행 됨.
내장된 톰캣 웹서버를 자체적으로 띄우면서 올라옴.


gradle 빌드 툴은 의존관계 관리를 해줌

spring-boot-starter-web 라이브러리만 받아오면 그외 필요한 것들을 다 당겨온다.
계속 파고들면서 의존관계에 있는 필요한 것들을 가져온다.
의존관계에 의해 서로 필요한 것들을 가져온다.


스프링 부트 라이브러리

  • spring-boot-starter-web
    -spring-boot-starter-tomcat: 톰캣 (웹서버)
    -spring-webmvc: 스프링 웹 MVC

  • spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)

  • spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
    -spring-boot
    ---- spring-core
    -spring-boot-starter-logging
    ---- logback, slf4j


테스트 라이브러리

  • spring-boot-starter-test
    ---- junit: 테스트 프레임워크
    ---- mockito: 목 라이브러리
    ---- assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
    ---- spring-test: 스프링 통합 테스트 지원

View 환경설정

welcome 페이지 만들기

(도메인 누르고 들어오는 첫화면)

src/main/resources/static/index.html

<!DOCTYPE HTML>
<html>
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
Hello
<a href="/hello">hello</a>
</body>
</html>
  • 스프링 부트가 제공하는 Welcome Page 기능
    static/index.html 을 올려두면 Welcome page 기능을 제공한다.

welcome 페이지 어떻게 하지? -> 문서 찾기
https://docs.spring.io/spring-boot/index.html

spring.io -> https://spring.io/projects/spring-boot#learn

https://docs.spring.io/spring-boot/reference/web/servlet.html#web.servlet.spring-mvc.welcome-page

thymeleaf 템플릿 엔진

thymeleaf 공식 사이트: https://www.thymeleaf.org/

스프링 공식 튜토리얼: https://spring.io/guides/gs/serving-web-content/

스프링부트 메뉴얼: https://docs.spring.io/spring-boot/reference/web/reactive.html#web.reactive.webflux.template-engines

thymeleaf 템플릿 장점 : html을 작성하고 그 파일을 서버 없이 바로 열어봐도 껍데기를 볼 수 있다.


빌드 및 실행

콘솔 실행
작업 폴더로 이동
.gradlew builld
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHOT.jar
실행 확인

나중에 서버 배포 할 때는 -> hello-spring-0.0.1-SNAPSHOT.jar 파일만 서버에 복사해서 넣고, java -jar ~~~ 해서 실행 하면 된다.


src/main/java/controller/HelloController

@Controller

public class HelloController {
    // 웹 애플리케이션에서 /hello 라고 들어오면 이 메소드 호출한다.
    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!!");
        return "hello";
    }

    @GetMapping("hello-mvc")
    // 외부에서 파라미타를 받음
    // 옵션 넣을 떄 단축키 -> command + p
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);   // 키 - name
        return "hello-template";
    }

    @GetMapping("hello-string")
    @ResponseBody
    // http 응답 body 부분에 "hello " + name 이 데이터를 직접 넣어 주겠다는 뜻
    public String helloString(@RequestParam("name") String name) {
        return "hello " + name; // 문자적은 것이 그대로 내려간다.
    }

    // json 방식으로 결과 나옴 -> 요즘은 다 JSON 반환으로 함
    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        // command + shift + enter 해주면 자동 완성
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    static class Hello {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

src/main/resources/templates/hello.html

<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
</html>
<!-- thymeㅣeaf 템플릿 장점은 html을 작성하고 그 파일을 서버 없이 바로 열어봐도 껍데기를 볼 수 있다. -->

MVC와 템플릿 엔진

  • MVC: Model, View (화면을 그리는데 집중), Controller (내부적 치리에 집중)

이후 localhost:8080/hello-mvc?name=spring 실행


API
@ResponseBody 객체 반환

이후 localhost:8080/hello-api?name=spring 실행
@ResponseBody 를 사용하고, 객체를 반환하면 객체가 JSON으로 변환됨

@ResponseBody 객체 반환 사용 원리

@ResponseBody 를 사용

  • HTTP의 BODY에 문자 내용을 직접 반환
  • viewResolver 대신에 HttpMessageConverter 가 동작
  • 기본 문자처리: StringHttpMessageConverter
  • 기본 객체처리: MappingJackson2HttpMessageConverter
  • byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음

빌드 및 실행

콘솔 실행
작업 폴더로 이동
.gradlew builld
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHOT.jar
실행 확인

나중에 서버 배포 할 때는 -> hello-spring-0.0.1-SNAPSHOT.jar 파일만 서버에 복사해서 넣고, java -jar ~~~ 해서 실행 하면 된다.


🍑 테스트 케이스 작성

스프링에 내장된 JUnit 프레임워크로 테스트 실행한다.

src/test/java 하위 폴더에 생성한다.

테스트 케이스 작성은 매우 중요

테스트는 순서대로 실행되는 것이 아닌 순서 상관없이 실행된다.

  • 테스트는 서로 의존관계 상관없이 순서 없이 설계가 되어야 한다.
  • 하나의 테스트가 끝나면 저장소나, 공용 데이터를 지워주어야 한다.
  • 테스트는 각각 독립적으로 실행되어야 한다.
  • 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다.

한번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트의 결과가 남을 수 있다. 이렇게
되면 다음 이전 테스트 때문에 다음 테스트가 실패할 가능성이 있다.
따라서

@AfterEach를 사용하여 각 테스트가 종료 될 때 마다 이 기능을 실행한다.
여기서는 메모리 DB에 저장된 데이터를 삭제한다.

	@AfterEach
    public void afterEach() {
        repository.clearStore();    // 저장소 데이터 클리어 시키기
    }

🌱 회원관리 백엔드 개발 시작

  • 비즈니스 요구사항 정리
  • 회원 도메인과 리포지토리(저장소 객체) 만들기
  • 회원 리포지토리 테스트 케이스 작성
  • 회원 서비스 개발
  • 회원 서비스 테스트 (JUnit)

비즈니스 요구사항 정리

  • 데이터 : 회원ID, 이름
  • 기능 : 회원등록, 조회
  • 데이터 저장소 미정 (추후 지정)

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 핵심 비즈니스 로직 구현
  • 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리

클래스 의존관계

  • 데이터 저장소 미정, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
  • 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
  • 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용 예정

회원 도메인과 리포지토리 만들기

회원 객체
domain/Member

package hello.hello_spring.domain;

import jakarta.persistence.*;

@Entity
public class Member {

    // 데이터 구분하기 위해서 시스템에 저장하는 id값
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

회원리포지토리 인터페이스
repository/MemberRepository

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository {

    // 회원을 저장하면 저장된 회원이 반환됨 (회원이 저장소에 저장)
    Member save(Member member);
    // id, name 으로 회원을 찾는 것
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    // 지금까지 저장된 모든 회원리스트를 다 반환함
    List<Member> findAll();
}

회원 리포지토리 메모리 구현체
repository/MemoryMemberRepository

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.springframework.stereotype.Repository;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);   // id 세팅
        store.put(member.getId(), member);  // store에 저장
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
        // null 반환될 가능성이 있다면 Optional 로 감싼다.
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore() {
        store.clear();
    }
}

회원 서비스 개발 (회원 비즈니스 로직)

package hello.hello_spring.service;

import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Transactional  // 데이터 저장 혹은 변경시에 반드시 필요
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * 회원가입
     */
    public Long join(Member member) {
        // 같은 이름이 있는 중복 회원 X
        validateDuplicateMember(member);    //  중복 회원 검증
        memberRepository.save(member);
        return member.getId();  // 아이디 반환
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

회원 서비스 테스트

package hello.hello_spring.service;

import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);

    }

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();    // 돌 떄마다 다 끝나고 나면 DB의 값을 다 날려준다.
    }

    // 테스트 코드에서는 한글로 작성해도 된다. 빌드 될 때 테스트 코드는 실제 코드에 포함 되지 않는다.
    @Test
    void 회원가입() {
        // 추천 문법 : given (뭔가가 주어졌다.) - when (이거를 실행 했을 때) - then (결과가 이것이 나와야 해)

        // given (이 데이터를 기반으로 하는 것이구나)
        Member member = new Member();
        member.setName("hello");

        // when (이거를 검증하는구나)
        Long saveId = memberService.join(member);

        // then (여기가 검증부)
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // give
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        // () -> memberService.join(member2) 이 로직을 실행하면, IllegalStateException 아 예외가 발생해야 함

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        // 메세지도 같은지 검증


        // then

    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

추천 문법 : given (뭔가가 주어졌다.) - when (이거를 실행 했을 때) - then (결과가 이것이 나와야 해)

  • given (이 데이터를 기반으로 하는 것이구나)

  • when (이거를 검증하는구나)

  • then (여기가 검증부)

이런 방식으로 테스트 케이스 작성하면 코드가 길어질 때 도움이 됨.

해당 클래스에서 command + shift + t -> 테스트 파일 껍데기를 자동으로 만들어주어서 시간 단축.


회원 관리예제 - 웹 MVC 개발

회원 웹 기능 - 홈 화면 추가

홈 컨트롤러 추가

package hello.hello_spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home";
    }
}

회원 관리용 홈화면

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<div class="container">
    <div>
        <h1>Hello Spring</h1>
        <p>회원 기능</p>
        <p>
            <a href="/members/new">회원 가입</a>
            <a href="/members">회원 목록</a>
        </p>
    </div>
</div> <!-- /container -->

</body>
</html>

회원 웹 기능 - 등록

회원 등록 폼 컨트롤러

package hello.hello_spring.controller;

import org.springframework.ui.Model;
import hello.hello_spring.domain.Member;
import hello.hello_spring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping("/members/new")
    public String createForm() {
        return "members/createMemberForm";
    }

    @PostMapping("/members/new")
    public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);
        return "redirect:/";    // 회원가입이 끝나면 홈으로 돌려보냄
    }

    @GetMapping("/members")
    public String list(Model model) {
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
}


회원 등록 폼

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<div class="container">

    <form action="/members/new" method="post">
        <div class="form-group">
            <label for="name">이름</label>
            <input type="text" id="name" name="name" placeholder="이름을 입력하세요">
        </div>
        <button type="submit">등록</button>
    </form>

</div> <!-- /container -->

</body>
</html>

회원 등록 컨트롤러

package hello.hello_spring.controller;

public class MemberForm {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

회원 웹 기능 - 조회

회원 리스트

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<div class="container">
    <div>
        <table>
            <thead>
            <tr>
                <th>#</th>
                <th>이름</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="member : ${members}">
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->

</body>
</html>

JPA

  • JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
  • JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
  • JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

build.gradle 파일에 JPA, h2 데이터베이스 관련 라이브러리 추가

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.h2database:h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

JPA 엔티티 매핑

package hello.hello_spring.domain;

import jakarta.persistence.*;

@Entity
public class Member {

    // 데이터 구분하기 위해서 시스템에 저장하는 id값
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

JPA 회원 리포지토리

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;

import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository {

    private final EntityManager em;   // JPA 는 EntityManager 로 모든 것이 동작함

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        List<Member> result = em.createQuery("select m from Member m", Member.class)
                .getResultList();
        return result;
    }
}

JPA를 사용하도록 스프링 설정 변경

package hello.hello_spring.service;

import hello.hello_spring.aop.TimeTraceAop;
import hello.hello_spring.repository.JdbcTemplateMemberRepository;
import hello.hello_spring.repository.JpaMemberRepository;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

//    private EntityManager em;
//
//    @Autowired
//    public SpringConfig(EntityManager em) {
//        this.em = em;
//    }

//    private final DataSource dataSource;


//    @Autowired
//    public SpringConfig(DataSource dataSource) {
//        this.dataSource = dataSource;
//    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }

    // @Bean
    // public MemberRepository memberRepository() {
        // return new MemoryMemberRepository();
        // return new JdbcTemplateMemberRepository(dataSource);
        // return new JpaMemberRepository(em);
    // }


//    @Bean
//    public TimeTraceAop timeTraceAop() {
//        return new TimeTraceAop();
//    }
}

스프링 데이터 JPA

스프링 데이터 JPA 회원 리포지토리

package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);
}

스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정 변경

package hello.hello_spring.service;

import hello.hello_spring.aop.TimeTraceAop;
import hello.hello_spring.repository.JdbcTemplateMemberRepository;
import hello.hello_spring.repository.JpaMemberRepository;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }

}

통합 테스트 파일 작성

package hello.hello_spring.service;

import hello.hello_spring.domain.Member;
import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    // 테스트 코드에서는 한글로 작성해도 된다. 빌드 될 때 테스트 코드는 실제 코드에 포함 되지 않는다.
    @Test
    void 회원가입() {
        // 추천 문법 : given (뭔가가 주어졌다.) - when (이거를 실행 했을 때) - then (결과가 이것이 나와야 해)

        // given (이 데이터를 기반으로 하는 것이구나)
        Member member = new Member();
        member.setName("spring");

        // when (이거를 검증하는구나)
        Long saveId = memberService.join(member);

        // then (여기가 검증부)
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // give
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        // () -> memberService.join(member2) 이 로직을 실행하면, IllegalStateException 아 예외가 발생해야 함

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        // 메세지도 같은지 검증


        // then

    }

}

H2 데이터 베이스 연결

  • chmod 755 h2.sh
  • ./h2.sh

데이터베이스 파일 생성 방법

  • jdbc:h2:~/test (최초 한번)
  • ~/test.mv.db 파일 생성 확인

이후부터는 jdbc:h2:tcp://localhost/~/test 이렇게 접속

테이블 생성

create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);


🌱 결과 화면

홈화면


회원 가입 화면


회원 목록 화면


정상적인 동작이 되는 것을 알 수 있다.

profile
Hello World!

0개의 댓글

관련 채용 정보