JUNEee

[Spring Boot] rest api 스프링부트 회원관리 기능 구현하기! (CRUD) 본문

BE/스프링

[Spring Boot] rest api 스프링부트 회원관리 기능 구현하기! (CRUD)

JUNEee 2025. 5. 29. 19:00
반응형

CRUD 란?

CRUD 란 Create, Read, Update, Delete 의 앞글자를 딴 개념으로 대부분의 시스템이 데이터를 처리할 때 기본적으로 제공하는 핵심 기능을 의미한다.
본 블로그에서는 CRUD를 각각

  • C (Create) : 회원가입
    R (Read) : 회원조회
    U (Update) : 회원정보 수정
    D (Delete) : 회원정보 삭제
    로 구현해볼 예정이다.

시작하기 전 준비하기

회원관리 기능을 구현하기 전 사전 준비작업이 필요하다.
먼저 사용할 기술스택은 다음과 같다

  1. Spring Boot
  2. MySQL
  3. Spring JPA
  4. Spring Security
  5. Swagger
  • 먼저 MySQL, Spring JPA, Spring Security, Swagger 사용을 위해 의존성을 추가해주어야 한다
    현재 추가된 의존성은 다음과 같다
    //build.gradle
      implementation 'org.springframework.boot:spring-boot-starter-security'
      implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8'
      implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
      runtimeOnly 'com.mysql:mysql-connector-j'
  • MySQL 연결
    //application.properties
      #mysql
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      spring.datasource.url=jdbc:mysql://localhost:3306/crud
      spring.datasource.username=
      spring.datasource.password=
      spring.jpa.hibernate.ddl-auto=update
  • Security 설정 (security를 사용하는 이유는 회원가입 시 패스워드 필드를 해싱하기 위함임)
    기본적으로 모든 api 경로를 허용해주었다. 또한 패스워드 필드를 해싱하기 위해 필요한 BCryptPasswordEncoder 객체를 의존성 주입받기 위하여 bean으로 등록해주었다.
    //SecurityConfig
    @Configuration
    public class SecurityConfig {
      @Bean
      public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
          http
                  .csrf(csrf -> csrf.disable())
                  .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                  .authorizeHttpRequests(auth -> auth
                          .anyRequest().permitAll());
                  return http.build();
      }
      @Bean
      public BCryptPasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
      }
    }
  • 프로젝트 구조
    crud
    ├── CrudApplication.java           // 메인 애플리케이션 클래스
    ├── config
    │   └── SecurityConfig.java        // 보안 설정 클래스
    ├── controller
    │   └── Controller.java            // 요청을 처리하는 컨트롤러
    ├── dto
    │   ├── FindDto.java               // 사용자 조회용 DTO
    │   ├── LoginDto.java              // 로그인용 DTO
    │   ├── SignUpDto.java             // 회원가입용 DTO
    │   └── UpdateDto.java             // 사용자 정보 수정용 DTO
    ├── entity
    │   └── UserEntity.java            // 사용자 엔티티 클래스
    ├── repository
    │   └── Repository.java            // 데이터베이스 접근 인터페이스
    └── service
      └── Service.java

회원 JPA 엔터티 생성

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
@Builder
@Table(name ="사용자")
public class UserEntity {
    @Id
    @Column(length = 255)
    private String id;

    @Column(length = 255)
    private String name;

    @Column(length = 255)
    private String password;
}

회원 JPA 레파지토리 생성

public interface Repository extends JpaRepository<UserEntity, String> {
    boolean existsById(String id);
}

[1] 회원가입 구현

  • controller

    @PostMapping("/signup")
      public ResponseEntity<?> signUp(@RequestBody SignUpDto signUpDto) {
          try {
              String signUpResponse = service.signUp(signUpDto);
              return ResponseEntity.ok().body(signUpResponse);
          } catch (RuntimeException e) {
              return ResponseEntity.badRequest().body(e.getMessage());
          }
      }
  • Dto

    public class SignUpDto {
      private String id;
      private String name;
      private String password;
    
      public UserEntity toEntity() {
          return UserEntity.builder()
                  .id(this.id)
                  .name(this.name)
                  .password(this.password)
                  .build();
      }
    }
  • Service

    public String signUp(SignUpDto signUpDto) {
          if(repository.existsById(signUpDto.getId())) {
              log.info("이미 가입된 유저입니다.");
              throw new RuntimeException("이미 가입된 유저입니다.");
          }
          signUpDto.setPassword(hash.encode(signUpDto.getPassword()));
          repository.save(signUpDto.toEntity());
    
          return "회원가입 성공";
      }
  • DB

[2] 회원조회 구현

  • controller

    @GetMapping("/{id}")
      public ResponseEntity<?> findById(@PathVariable String id) {
          System.out.println(id);
          try {
              FindDto user = service.findById(id);
              return ResponseEntity.ok().body(user);
          } catch (RuntimeException e) {
              return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
          }
    
      }
    
      @GetMapping("/all")
      public ResponseEntity<?> findAll() {
          List<FindDto> users = service.findAll();
          return ResponseEntity.ok().body(users);
      }
  • Dto

    @Getter
    @Setter
    @Builder
    public class FindDto {
      private String id;
      private String name;
    }
  • Service

    public List<FindDto> findAll() {
          List<UserEntity> users =  repository.findAll();
          return users.stream()
                  .map(user -> FindDto.builder()
                          .id(user.getId())
                          .name(user.getName())
                          .build())
                  .toList();
      }
    
      public FindDto findById(String id) {
          UserEntity user = repository.findById(id).orElse(null);
          if(user == null) {
              log.info("해당 ID의 유저가 없습니다.");
              throw new RuntimeException("해당 ID의 유저가 없습니다.");
          } else {
              log.info("유저 정보 조회 성공");
              return FindDto.builder()
                      .id(user.getId())
                      .name(user.getName())
                      .build();
          }
      }

[3] 회원수정 구현

  • controller
    @PatchMapping("/{id}")
      public ResponseEntity<?> update(@PathVariable String id, @RequestBody UpdateDto updateDto) {
          try {
              String updateResponse = service.update(id, updateDto);
              return ResponseEntity.ok().body(updateResponse);
          } catch (RuntimeException e) {
              return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
          }
      }
  • Dto
    @Getter
    @Setter
    public class UpdateDto {
      private String name;
      private String password;
    }
  • Service
    public String update(String id, UpdateDto updateDto) {
          if(!repository.existsById(id)) {
              log.info("해당 ID의 유저가 없습니다.");
              throw new RuntimeException("해당 ID의 유저가 없습니다.");
          } else {
              UserEntity user = repository.findById(id).orElse(null);
              user.setName(updateDto.getName());
              user.setPassword(hash.encode(updateDto.getPassword()));
              repository.save(user);
              return "유저 정보 업데이트 성공";
          }
      }

[4] 회원삭제 구현

  • controller
    @DeleteMapping("/{id}")
      public ResponseEntity<?> delete(@PathVariable String id) {
          try {
              String deleteResponse = service.delete(id);
              return ResponseEntity.ok().body(deleteResponse);
          } catch (RuntimeException e) {
              return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
          }
      }
  • Dto
    @Getter
    @Setter
    public class UpdateDto {
      private String name;
      private String password;
    }
  • Service
    public String update(String id, UpdateDto updateDto) {
          if(!repository.existsById(id)) {
              log.info("해당 ID의 유저가 없습니다.");
              throw new RuntimeException("해당 ID의 유저가 없습니다.");
          } else {
              UserEntity user = repository.findById(id).orElse(null);
              user.setName(updateDto.getName());
              user.setPassword(hash.encode(updateDto.getPassword()));
              repository.save(user);
              return "유저 정보 업데이트 성공";
          }
      }

[5] 로그인 구현

  • controller

    @PostMapping("/login")
      public ResponseEntity<?> login(@RequestBody LoginDto loginDto) {
          try {
              String LoginResponse = service.login(loginDto);
              return ResponseEntity.ok().body(LoginResponse);
          } catch (RuntimeException e) {
              return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
          }
      }
  • Dto

    @Getter
    @Setter
    public class LoginDto {
      private String id;
      private String password;
    }
  • Service

    public String login(LoginDto loginDto) {
    
          if(!repository.existsById(loginDto.getId())) {
              log.info("가입된 유저가 없습니다.");
              throw new RuntimeException("가입된 유저가 없습니다.");
          }
    
          UserEntity user = repository.findById(loginDto.getId()).orElse(null);
          if(hash.matches(loginDto.getPassword(), user.getPassword())) {
              log.info("로그인 성공");
              return "로그인 성공";
          } else {
              log.info("비밀번호가 일치하지 않습니다.");
              throw new RuntimeException("비밀번호가 일치하지 않습니다.");
          }
      }

구현 결과

api 테스트는 postman 으로 진행하겠다.

  • 회원가입
  • 회원조회
  • 회원정보 수정
  • 회원정보 삭제
  • 로그인
반응형