비즈니스 요구사항 정리
- 데이터 : 회원 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 |