🎯 오늘의 목표
- 회원(User) 도메인 개발
⚙️ 진행한 작업
- 회원(User) 엔티티 생성
- 회원(User) 레파지토리 생성
- 회원(User) 서비스 생성
🛠️ 개발내용
📌 회원(User) 엔티티 생성
package ddururi.bookbookclub.domain.user.entity;
import ddururi.bookbookclub.domain.user.enums.Role;
import ddururi.bookbookclub.domain.user.enums.UserStatus;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Setter
(unique = true, nullable = false)
private String nickname;
@Enumerated(EnumType.STRING)
private Role role;
@Enumerated(EnumType.STRING)
private UserStatus status;
@Setter
@Column(length = 500)
private String bio;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
// 정적 생성 메서드
public static User create(String email, String encodedPassword, String nickname) {
User user = new User();
user.email = email;
user.password = encodedPassword;
user.nickname = nickname;
user.role = Role.USER;
user.status = UserStatus.ACTIVE;
return user;
}
}
- 롬복을 사용하여 클래스 전체에 @Getter를 주고 변경 가능한 필드인 닉네임, 자기소개 컬럼에만 @Setter를 줌.
- Auditing 기능을 사용하여 생성날짜, 마지막 저장날짜를 자동으로 저장.
- 정적 생성 메서드를 만들어, 불변성 보장 + 생성 책임을 명확히 함.
- @NoArgsConstructor(access = AccessLevel.PROTECTED)
- 파라미터가 없는 기본 생성자를 protected 접근제한자로 지정하여 외부에서 new User() 하지 못하게 막아줌.
🙋♀️ 왜 protected 생성자를 쓰는 걸까?
이유 | 설명 |
JPA가 필요로 함 | JPA는 리플렉션으로 객체를 생성하기 때문에 기본 생성자 필수 |
객체 무분별한 생성 방지 | new User()로 아무 값 없이 객체 생성되는 걸 방지하고, Builder나 생성자만으로 생성하게 유도 |
도메인 무결성 유지 | 필수 값 없이 생성되면 안 되는 엔티티에서 실수 방지 가능 |
📌 Auditing 기능 (정의, 사용법, 사용하는 이유 등)
[Spring/JPA] Auditing 기능이란? (정의, 사용법, 사용하는 이유 등)
Auditing 기능은 Spring Data JPA에서 누가 언제 어떤 작업을 했는지를 자동으로 기록해주는 기능.(즉, 어떤 사람이 게시글을 생성하거나 수정했을 때, 그 생성 일시, 수정 일시, 작성자, 수정자 같은 정
ddururiiiiiii.tistory.com
📌 @Builder란? (Feat.정적 생성 메서드)
[Spring] @Builder란? (Feat. 정적 생성 메서드)
@Builder는 롬복(Lombok)에서 제공하는 애노테이션 중 하나로,복잡한 객체를 간편하고 가독성 좋게 생성할 수 있도록 도와줌. @Builder를 사용하는 이유?[기존방식]User user = new User("홍길동", "hello@naver.co
ddururiiiiiii.tistory.com
📌 회원(User) 엔티티 와 연관된 enum 클래스 생성
1. Role
package ddururi.bookbookclub.domain.user.enums;
import lombok.Getter;
@Getter
public enum Role {
USER("일반 사용자"),
ADMIN("관리자");
private final String description;
Role(String description) {
this.description = description;
}
}
2. UserStatus
package ddururi.bookbookclub.domain.user.enums;
import lombok.Getter;
@Getter
public enum UserStatus {
ACTIVE("활동 중"),
WITHDRAWN("탈퇴");
private final String description;
UserStatus(String description) {
this.description = description;
}
}
📌 회원(User) 레파지토리 생성
package ddururi.bookbookclub.domain.user.repository;
import ddururi.bookbookclub.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
//이메일로 사용자 찾기
Optional<User> findByEmail(String email);
//이메일 중복 여부 확인 (회원가입 시)
boolean existsByEmail(String email);
}
- JPA Repository를 사용해서 보다 간결하게 작성
📌 JPA Repository란? (정의, 구조, 사용하는 이유, 사용법 등)
[Spring JPA]JPA Repository란? (정의, 구조, 사용하는 이유, 사용법 등)
🌱 JPA Repository란?JPA Entity 객체를 데이터베이스 테이블과 매핑해서, 이를 기반으로 자동으로 DB 쿼리를 처리해주는 인터페이스임! 🧱 기본 구조public interface UserRepository extends JpaRepository { } User :
ddururiiiiiii.tistory.com
📌 회원(User) 서비스 생성
package ddururi.bookbookclub.domain.user.service;
import ddururi.bookbookclub.domain.user.dto.UserLoginRequest;
import ddururi.bookbookclub.domain.user.dto.UserResponse;
import ddururi.bookbookclub.domain.user.dto.UserSignupRequest;
import ddururi.bookbookclub.domain.user.dto.UserUpdateRequest;
import ddururi.bookbookclub.domain.user.entity.User;
import ddururi.bookbookclub.domain.user.repository.UserRepository;
import ddururi.bookbookclub.global.exception.DuplicateEmailException;
import ddururi.bookbookclub.global.exception.UserNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
//회원가입
public UserResponse signup(UserSignupRequest request){
validateDuplicateEmail(request.getEmail());
User user = User.create(
request.getEmail(),
passwordEncoder.encode(request.getPassword()),
request.getNickname()
);
userRepository.save(user);
return UserResponse.from(user);
}
//이메일 중복 확인
private void validateDuplicateEmail(String email) {
if (userRepository.existsByEmail(email)) {
throw new DuplicateEmailException();
}
}
//로그인
public UserResponse login(UserLoginRequest request){
User user = validateUserLogin(request.getEmail(), request.getPassword());
return UserResponse.from(user);
}
//비밀번호 확인
private User validateUserLogin(String email, String rawPassword) {
User user = userRepository.findByEmail(email)
.orElseThrow(UserNotFoundException::new);
if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
return user;
}
// 회원정보 수정 (닉네임, 자기소개)
@Transactional
public void updateProfile(Long userId, UserUpdateRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(UserNotFoundException::new);
user.setNickname(request.getNickname());
user.setBio(request.getBio());
// save 호출 안 해도 됨 → JPA가 변경감지로 UPDATE 수행
}
}
- validateDuplicateEmail(), validateUserLogin() 등 유효성 검사하는 메서드를 따로 만들어서 조금 더 간결하게 작성.
📌 스프링 시큐리티 - 비밀번호 암호화
[스프링 시큐리티] 비밀번호 암호화 하기 (BCryptPasswordEncoder)
1. 비밀번호 암호화란?비밀번호 **암호화(Encryption)**는 사용자의 비밀번호를 데이터베이스에 저장할 때 읽을 수 없는 형태로 바꾸는 것.하지만 정확히 말하면, 우리가 흔히 사용하는 방식은 암호
ddururiiiiiii.tistory.com
📌 BCryptPasswordEncoder 란?
[Spring Security] BCryptPasswordEncoder란? (정의, 사용하는 이유, 사용법 등)
🔐 BCryptPasswordEncoder란?Spring Security에서 제공하는 비밀번호 해싱 클래스.즉, 회원가입 시 비밀번호를 안전하게 저장하고, 로그인 시 비밀번호를 안전하게 검증하기 위해 사용하는 암호화 도구비
ddururiiiiiii.tistory.com
📌 회원정보 수정 메서드에만 @Transactional 어노테이션을 붙인 이유는?
- JPA는 트랜잭션 안에서 user.setNickname() 이런 필드 변경을 감지하고 메서드가 끝나면서 트랜잭션이 커밋될 때 → 자동으로 update 쿼리를 날려주는데 트랜잭션이 없으면 변경을 감지해도 실제 DB에 반영되지 않음 그래서 update, delete 작업할 땐 무조건 @Transactional 붙여줘야 함.
📌 save()는 @Transactional 없어도 되는 걸까?
- Spring Data JPA의 save() 자체가 내부적으로 트랜잭션을 지원해서 안 붙여줘도 됨.
📌 예외적으로 꼭 붙여야 하는 경우는?
✅ 1. save + 다른 작업이 함께 있는 경우
@Transactional
public void register(User user) {
userRepository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
}
- 이메일 보내다가 에러 나면 전체 롤백되어야 하기 때문에 이럴 땐 묶어서 트랜잭션 처리해야 하므로 @Transactional 필요
✅ 2. 변경 감지(update), delete, lazy loading 등은 반드시 필요
@Transactional
public void updateUser(Long id) {
User user = userRepository.findById(id).get();
user.setNickname("변경됨");
// 트랜잭션 끝날 때 update 쿼리 발생
}
- 트랜잭션 없으면 → 변경 감지를 못함 → update 쿼리 안 날라감 ❌
📌 회원(User) 서비스 관련 DTO 생성
1. UserLoginRequest
package ddururi.bookbookclub.domain.user.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginRequest {
private String email;
private String password;
}
2. UserSignupRequest
package ddururi.bookbookclub.domain.user.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserSignupRequest {
private String email;
private String password;
private String nickname;
}
3. UserUpdateRequest
package ddururi.bookbookclub.domain.user.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserUpdateRequest {
private String nickname;
private String bio;
}
4. UserResponse
package ddururi.bookbookclub.domain.user.dto;
import ddururi.bookbookclub.domain.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
@AllArgsConstructor
public class UserResponse {
private Long id;
private String email;
private String nickname;
private String role;
public static UserResponse from(User user) {
return UserResponse.builder()
.id(user.getId())
.email(user.getEmail())
.nickname(user.getNickname())
.role(user.getRole().name())
.build();
}
}
- 정적 생성 메서드 + @Builder 조합 사용
📌 @Builder, 정적 생성 메서드 ?
[Spring] @Builder란? (Feat. 정적 생성 메서드)
@Builder는 롬복(Lombok)에서 제공하는 애노테이션 중 하나로,복잡한 객체를 간편하고 가독성 좋게 생성할 수 있도록 도와줌. @Builder를 사용하는 이유?[기존방식]User user = new User("홍길동", "hello@naver.co
ddururiiiiiii.tistory.com
📌 회원(User) 서비스 관련 예외 클래스 생성
1. DuplicateEmailException
package ddururi.bookbookclub.global.exception;
public class DuplicateEmailException extends RuntimeException {
public DuplicateEmailException() {
super("이미 가입된 이메일입니다.");
}
}
2. UserNotFoundException
package ddururi.bookbookclub.global.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
super("사용자를 찾을 수 없습니다.");
}
}
📌 회원(User) 서비스 관련 비밀번호 암호화 설정 클래스 생성
1 . SecurityConfig
package ddururi.bookbookclub.global.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
'💻 뚝딱뚝딱 > 북북클럽' 카테고리의 다른 글
[개발일지 #005] 로그인 구현 (Feat.JWT 기반 인증) (0) | 2025.04.22 |
---|---|
[개발일지 #004] 회원 정보 수정 API 구현 (0) | 2025.04.22 |
[개발일지 #003] 회원(User) 도메인 회원가입 API 구현 및 테스트 (0) | 2025.04.22 |
[개발일지#002] 회원(User) 도메인 단위 테스트 (0) | 2025.04.18 |
[개발일지#000] 프로젝트 생성 (프로젝트 생성, MySQL 연결, 개발 편의 설정, 패키지 설정 등) (0) | 2025.04.16 |