Framework/Spring

[Spring] 회원 관리 예제

IT수정 2024. 10. 31. 14:32

비즈니스 요구사항 정리

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원등록, 조회

 

애플리케이션 계층 구조

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

 

회원 도메인과 리포지토리 생성

Member.java (도메인 = DAO)

package hellov2.hellov2_spring.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity //Java 클래스와 데이터베이스 테이블 간의 매핑을 정의
@Setter @Getter //Setter, Getter 자동 생성
public class Member {

    @Id //테이블의 기본키 정의
    @GeneratedValue(strategy = GenerationType.IDENTITY) //기본키 값 자동 생성. IDENTITY : 자동 증가(auto increment)
    private Long id;

    @Column(name="userid") //속성 이름을 지정해줄 때 사용하는 어노테이션
    private String userId; //속성에 대문자가 들어가면 자동으로 언더바 생성됨

    private String name;


}
Ctrl + Shift + T : 새 테스트 생성

 

MemberRepository.java

package hellov2.hellov2_spring.repository;

import hellov2.hellov2_spring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
//JPA를 사용하여 데이터베이스와 상호작용하기 위한 기본적인 CRUD 기능을 제공하는 리포지토리
//별도로 CRUD 메서드를 구현하지 않아도, save(), findById(), findAll(), deleteById()와 같은 메서드를 사용할 수 있다.

//Member는 이 Repository가 관리할 엔티티 클래스
//Long은 그 엔티티의 ID 타입. 즉, Member 엔티티의 기본 키의 데이터 타입이 Long이라는 의미

    //사용자 아이디로 조회 기능 추가
    Optional<Member> findByUserId(String userId);
}

//Optional : 존재할 수도 있고 존재하지 않을 수도 있는 경우를 안전하게 처리하기 위한 컨테이너
//주로 null 값을 다룰 때 발생할 수 있는 NullPointerException을 방지하기 위해 사용

 

DTO 생성

MemberForm.java

package hellov3.hellov3_spring.dto;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class MemberForm {

    private String userid;

    private String name;
}

 

서비스 생성

MemberService.java

package hellov3.hellov3_spring.service;

import hellov3.hellov3_spring.domain.Member;
import hellov3.hellov3_spring.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

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

@Service //해당 클래스가 서비스 레이어의 컴포넌트임을 의미
@RequiredArgsConstructor //생성자 자동 생성. 단 final 필드나 @NonNull이 붙은 필드여야 함
public class MemberService {
    private final MemberRepository memberRepository;
    //memberRepository는 객체가 생성된 후, 더 이상 다른 객체로 변경되지 않는다.
    //이를 통해 해당 변수를 사용하는 코드가 항상 같은 인스턴스를 참조하게 되어 예기치 않은 변경으로 인한 오류를 방지할 수 있다.

    //회원 가입
    public Long join(Member member) {
        validateDuplicateMember(member);
        this.memberRepository.save(member);
        return member.getId();
    }

    //중복회원 검증
    private void validateDuplicateMember(Member member) {
        Optional<Member> findMember = memberRepository.findByUserid(member.getUserid());
        if(findMember.isPresent()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }

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

    //회원 한명 조회
    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }

    //회원 삭제
    public void delete(Member member) {
        this.memberRepository.deleteById(member.getId());
    }
}

 

@Service
Spring의 어노테이션으로, 해당 클래스가 서비스 레이어의 컴포넌트임을 나타낸다. 서비스 레이어는 비즈니스 로직을 처리하는 곳으로, 주로 데이터베이스와의 상호작용, 트랜잭션 관리 등을 담당한다. 이 어노테이션이 있으면 Spring이 이 클래스를 자동으로 빈(Bean)으로 등록한다.

Service 레이어를 사용하는 이유
비즈니스 로직 분리 : 데이터 접근 코드(Repository)와 비즈니스 로직을 분리하여 코드의 가독성과 유지보수성을 높임
재사용성 : 여러 컨트롤러에서 같은 비즈니스 로직을 사용할 수 있도록 함
테스트 용이성 : 비즈니스 로직을 서비스 레이어에 모아두면, 유닛 테스트가 더 쉬워짐

비즈니스 로직
애플리케이션의 주요 기능이나 규칙을 정의하는 로직을 의미. 예를 들어, 회원 가입 시 중복 검사를 하거나, 특정 조건에 따라 할인율을 적용하는 등의 규칙이 포함된다. 비즈니스 로직은 도메인 모델에 따라 다르며, 애플리케이션의 핵심 기능을 형성한다.

빈 (Bean)
Spring에서 관리되는 객체를 의미. Spring IoC(제어의 역전) 컨테이너에 의해 생성되고 관리되며, 특정 생명주기와 범위를 가진다. 예를 들어, @Service, @Controller, @Repository와 같은 어노테이션이 붙은 클래스는 Spring에 의해 빈으로 등록되어 애플리케이션에서 사용할 수 있다.

 

테스트 생성

Membertest.java

package hellov2.hellov2_spring.domain;

import hellov2.hellov2_spring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

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

@SpringBootTest
class MemberTest {

    @Autowired private MemberRepository memberRepository;

    @Test
    void 멤버저장() {
        Member member1 = new Member();
        member1.setUserId("test");
        member1.setName("사용자1");
        this.memberRepository.save(member1);
    }

    @Test
    void 아이디로찾기() {
        Optional<Member> findMember = this.memberRepository.findByUserId("test");

        if(findMember.isPresent()) {
            Member member = findMember.get();
            assertEquals("test", member.getUserId());
        }

    }
}

 

웹 MVC 개발

홈 화면 생성

home.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring</title>
    <link href="/css/bootstrap.css" rel="stylesheet">
</head>
<body class="vh-100 d-flex flex-column justify-content-center align-content-center">
    <div class="container text-center">
        <h1>Hello Spring</h1>
        <p class="py-3">Hello Spring에 오신 것을 환영합니다.</p>
        <a href="/members/new" class="btn btn-primary">회원 가입</a>
        <a href="/members" class="btn btn-primary">회원 목록</a>
    </div>
</body>
</html>

 

HomeController.java

package hellov3.hellov3_spring.controller;

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

@Controller
public class HomeController {

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

}

 

회원가입 화면 생성

createMemberForm.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>회원 가입</title>
    <link href="/css/bootstrap.css" rel="stylesheet">
</head>
<body class="bg-body-tertiary vh-100 d-flex flex-column justify-content-center align-content-center">
    <div class="container" style="width: 500px;">
        <div class="bg-white border rounded-4 p-5">
            <h1 class="pb-3">회원 가입</h1>
            <form action="/members/new" method="post">
                <div class="mb-3">
                    <label for="userid" class="form-label">아이디</label>
                    <input type="text" class="form-control" id="userid" name="userid" placeholder="아이디를 입력하세요" required>
                </div>
                <div class="mb-3 pb-3">
                    <label for="name" class="form-label">이름</label>
                    <input type="text" class="form-control" id="name" name="name" placeholder="이름을 입력하세요" required>
                </div>
                <div class="text-end">
                    <input type="reset" class="btn btn-outline-primary" value="리셋">
                    <input type="submit" class="btn btn-primary" value="등록">
                </div>
            </form>
        </div>
    </div>
</body>
</html>

 

회원관리 화면 생성

memberList.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>회원 목록</title>
    <link href="/css/bootstrap.css" rel="stylesheet">
</head>
<body class="bg-body-tertiary vh-100 d-flex flex-column justify-content-center align-content-center">
    <div class="container" style="width: 800px;">
        <div class="bg-white border rounded-4 p-5">
            <h1 class="pb-4">회원 목록</h1>
            <table class="table text-center">
                <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">아이디</th>
                    <th scope="col">이름</th>
                    <th scope="col"></th>
                </tr>
                </thead>
                <tbody>
                <tr class="align-middle" th:each = "member : ${members}">
                    <!-- 받은 변수를 각각 돌려 줄 지역변수 : ${받은 변수} -->
                    <th scope="row" th:text="${member.getId()}"></th>
                    <td th:text="${member.getUserid()}"></td>
                    <td th:text="${member.getName()}"></td>
                    <td><a th:href="@{/members/{memberid}/delete(memberid = ${member.id})}" class="btn btn-danger">삭제</a></td>
                </tr>
                </tbody>
            </table>
            <div class="text-end">
                <a href="/" class="link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover">뒤로 가기</a>
            </div>
        </div>
    </div>
</body>
</html>

 

회원 컨트롤러 생성

MemberController.java

package hellov3.hellov3_spring.controller;

import hellov3.hellov3_spring.domain.Member;
import hellov3.hellov3_spring.dto.MemberForm;
import hellov3.hellov3_spring.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

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

@Controller
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

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

    //html - (MemberForm) Controller - (Member) Service - Repository
    @PostMapping("/members/new")
    public String createMember(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());
        member.setUserid(form.getUserid());
        memberService.join(member);
        return "redirect:/";
    }

    //회원 목록으로 가면 MemberRepository에 있는 모든 회원들을 조회
    //MemberService에 있는 findMember() 메서드를 사용
    @GetMapping("/members")
    public String memberList(Model model) { //Model : 뷰에 데이터를 전달하기 위해 사용
        List<Member> members = memberService.findMember();
        model.addAttribute("members", members);
        return "members/memberList";
    }

    //삭제 버튼을 클릭하면 회원 삭제, 그 후 다시 회원 목록 페이지로 이동
    @GetMapping("/members/{memberid}/delete")
    public String deleteMember(@PathVariable("memberid") Long memberid) { //가져온 memberid를 지역변수로 넣기
        Optional<Member> findMember = memberService.findOne(memberid);//지역변수에 넣은 memberid로 일치하는 id 찾기
        if(findMember.isPresent()){
            memberService.delete(findMember.get()); //일치하는 id가 있다면, 해당 id 삭제
        }
        return "redirect:/members";
    }

}

 

 

경로

 

 

'Framework > Spring' 카테고리의 다른 글

[Spring] 회원 가입과 로그인 예제  (1) 2024.11.04
[Spring] HTML 구성 요소의 통합  (2) 2024.10.31
[Spring] H2 서버 연결하기  (0) 2024.10.30
[Spring] MVC 패턴  (0) 2024.10.30
[Spring] IntelliJ IDEA 설정  (0) 2024.10.29