프로젝트 내용 정리/개인 프로젝트

[Spring boot] (에브리타임 클론코딩) + MySQL, 회원 가입 구현(1)

sun_young 2023. 7. 13. 23:14

[참고] '백견불여일타 - 스프링 부트 쇼핑몰 프로젝트 with JPA' 책을 참고하여 작성된 코드입니다!

https://product.kyobobook.co.kr/detail/S000001624717

 

백견불여일타 스프링 부트 쇼핑몰 프로젝트 with JPA | 변구훈 - 교보문고

백견불여일타 스프링 부트 쇼핑몰 프로젝트 with JPA | 스프링 부트와 JPA를 활용하여 실제 이커머스 업계에서 활용되는 쇼핑몰 기술들을 직접 구현해볼 수 있게 구성하였다. JPA와 Thymeleaf에 대한

product.kyobobook.co.kr

 

1. 버전

spring boot 2.7.13

java 11

gradle

IntelliJ IDEA

 

2. 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-security'
runtimeOnly 'com.mysql:mysql-connector-j'

 

3. ERD

자유 게시판만 추가했지만 나머지 게시판도 크게 다른 점은 없음

(주제는 에브리타임 클론코딩이지만 세부적인 내용은 능력 부족으로ㅠ 약간 수정될 것 같다)

 

4. MySQL 연동

① 스키마 생성

 

② application.properties - MySQL 정보 입력

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/everytime?serverTimezone=UTC
spring.datasource.username=###
spring.datasource.password=###

spring.jpa.hibernate.ddl-auto=update

두번째 datasource.url 부분에 everytime은 본인이 설정한 스키마 이름으로 변경하면 되고, username과 password도 본인 정보에 맞게 수정하면 된다.

 

5. 스프링 시큐리티 설정

@Configuration 
@EnableWebSecurity // Spring Security 설정
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

URL에 따른 인증 및 인가 추가는 추후 진행 예정

 

6. dto 생성

package com.example.everytime.DTO;

import lombok.*;

@Getter @Setter
public class UserDto {
    private String userId;
    private String userPwd;
    private String userUniv;
    private String userEmail;
}

 

7. entity 생성

package com.example.everytime.constant;

public enum Role {
    USER,ADMIN
}
package com.example.everytime.entity;

import com.example.everytime.DTO.UserDto;
import com.example.everytime.constant.Role;
import lombok.*;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.*;

@Getter @Setter
@Entity
@Table(name = "user")
@ToString
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, length = 50, unique = true)
    private String userId;

    @Column(nullable = false, length = 50)
    private String userPwd;

    @Column(nullable = false)
    private String userUniv;

    @Column(nullable = false, length = 50, unique = true)
    private String userEmail;

    @Enumerated(EnumType.STRING)
    private Role role;

    public static User createUser(UserDto userDto, PasswordEncoder passwordEncoder) {
        User user = new User();
        user.setUserId(userDto.getUserId());
        user.setUserEmail(userDto.getUserEmail());
        user.setUserUniv(userDto.getUserUniv());
        String password = passwordEncoder.encode(userDto.getUserPwd());
        user.setUserPwd(password);
        user.setRole(Role.USER);
        return user;
    }
}

처음이라 책의 예제와 최대한 유사하게 구현하려고 id와 role이 추가되었는데 추후 앞에 설계한 erd에 맞춰 수정할 예정 (본 프로젝트에서는 관리자 버전을 따로 만들지 않을거라 role도 필요없지 않을까 싶다)

 

@Table(name = "") : "" 안에 적힌 이름으로 MySQL table 생성

@Id : 테이블 기본키 설정

@GeneratedValue : 자동으로 값 증가

@Column : 테이블 열 설정 

createUser 함수 : 매개변수로 넘겨받은 dto를 entity로 변경, 비밀번호는 암호화해서 db에 저장

 

8. repository 생성

package com.example.everytime.repository;

import com.example.everytime.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUserId(String userId);
}

기본적으로 JpaRepository를 상속받고, 아이디가 중복되면 안되기 때문에 입력한 아이디로 이미 가입한 회원이 있는지 확인하기 위해 findByUserId를 추가했다

 

9. service 생성

package com.example.everytime.service;

import com.example.everytime.entity.User;
import com.example.everytime.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
@Transactional
public class UserService {
    private final UserRepository userRepository;

    public User saveUser(User user) {
        validateDuplicateUser(user);
        return userRepository.save(user);
    }

    private void validateDuplicateUser(User user) {
        User findUser = userRepository.findByUserId(user.getUserId());
        if(findUser != null) {
            throw new IllegalStateException("이미 가입된 회원입니다.");
        }
    }

}

@RequiredArgsConstructor : final이나 @NonNull이 붙은 필드에 생성자를 생성해준다. 빈에 생성자가 1개이고 생성자의 파라미터 타입이 빈으로 등록이 가능하다면 @Autowired 어노테이션 없이 의존성 주입이 가능하다.

 

10. test 작성

package com.example.everytime.service;

import com.example.everytime.DTO.UserDto;
import com.example.everytime.entity.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
public class UserServiceTest {
    @Autowired
    UserService userService;

    @Autowired
    PasswordEncoder passwordEncoder;

    public User createUser() {
        UserDto userDto = new UserDto();
        userDto.setUserId("kim1111");
        userDto.setUserEmail("abcdefg@naver.com");
        userDto.setUserUniv("**대학교");
        userDto.setUserPwd("kim1111");
        return User.createUser(userDto, passwordEncoder);
    }

    @Test
    @DisplayName("회원가입 테스트")
    public void saveUserTest() {
        User user = createUser();
        User savedUser = userService.saveUser(user);

        Assertions.assertEquals(user.getId(), savedUser.getId());
    }
}

테스트 결과는 성공!