다른 팀원들과 프로젝트를 조율하고 진행사항을 효과적으로 추적하기 위해 위와 같이 BE 파트의 Milestone
을 정리하고, 각 파트 또한 정리를 요청하였다.
또한 위와 같이 팀 Slack에 Github을 연동하여 Repo의 변화를 추적할 수 있게 하였다
Spring Boot
, FastAPI
서버에서 기능 구현을 위해 필요한 요소 정리
React-Native
<> Spring Boot
<> FastAPI
양방향 Socket 필요Socket 처리 결과 스크립트 추가와 동시에 자막 표시
특정 키워드 하이라이트 자막 표시
수정된 MVP 모델은 위와 같다
기존의 Express.js 서버의 역할을 Sprigng Boot가 대신하고
MariaDB, MongoDB 모두를 활용하여 여러 종류의 데이터에 대한 유지보수성을 높였다
또한 기존의 Node.js 서버에서 Spring 서버로 변경한 이유는 Single Thread라는 Node.js 서버의 단점 때문이다. 현 서비스의 특성상 실시간 데이터 전송이 빈번하고 전송되는 데이터의 양도 작지 않기 때문에 Muli Threading을 지원하는 Spring Boot로 서버를 변경하였다.
또한 추후 프로젝트의 규모가 커질 경우를 대비하여 코드 구조적으로 더 안정적이고, 테스트 환경 또한 더욱 자세한 Spring Boot로 서버를 변경하였다.
요구 데이터는 위와 같이 MariaDB에 저장할 정형 데이터와 MongoDB에 저장할 비정형 데이터로 나누었다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
위와 같은 Dependencies
를 추가하여 프로젝트를 생성한다.
> Could not resolve all files for configuration ':classpath'.
> Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.2.3.
Required by:
project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.2.3
이때 위와 같은 에러가 발생한다면 아래 코드처럼 sourceCompatibility
를 17로 변경하고
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.3'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.hearus'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
Gradle의 JVM 버전을 17로 변경해준 후
Project의 SDK 버전을 17로 변경하면
Download https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.10.2/junit-platform-engine-1.10.2-sources.jar, took 10 ms (142.16 kB)
Download https://repo.maven.apache.org/maven2/org/objenesis/objenesis/3.3/objenesis-3.3-sources.jar, took 10 ms (52.69 kB)
BUILD SUCCESSFUL in 59s
위와 같이 정장적으로 Gradle Build가 완료되는 것을 볼 수 있다.
java.lang.IllegalStateException: No Docker Compose file found in directory
또한 Spring Boot 프로젝트 생성 시 Docker 지원을 활성화했기 때문에 별도로 dockerfile을 설정해주지 않아 위와 같은 에러가 발생하였다.
// application.properties
spring.application.name=HEARUS-SPRING
# Deactivate Docker
spring.docker.compose.enabled=false
이는 application.properties
에 spring.docker.compose.enabled
를 명시해 해결한다.
CREATE DATABASE hearus_test;
먼저 Signup Logic을 구현하기 위해, hearus_test
DB를 생성해준다
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.mariadb.jdbc:mariadb-java-client'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
이후 위와 같이 'org.mariadb.jdbc:mariadb-java-client'
의존성을 추가해준다.
# MariaDB Properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/hearus_test
spring.datasource.username=root
spring.datasource.password=root
이때 DB에 관한 정보를 private하게 관리해주기 위해 위와 같이 Profile을 생성하고
spring.application.name=HEARUS-SPRING
# Deactivate Docker
spring.docker.compose.enabled=false
# Private Profile Include
spring.profiles.include=private
생성한 Private Profile을 application.properties
에 추가한다.
...
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Application Properties ###
application-private.properties
이후 Private Profile에 관한 정보를 .gitginore
에 추가해주어 정보를 숨긴다.
2024-03-13T21:22:05.708+09:00 INFO 24456 --- [HEARUS-SPRING] [ restartedMain] c.h.h.HearusSpringApplication : Starting HearusSpringApplication using Java 17.0.9 with PID 24456 (C:\Users\judem\Git\HEARUS\HEARUS-SPRING-BACKEND\build\classes\java\main started by judem in C:\Users\judem\Git\HEARUS\HEARUS-SPRING-BACKEND)
2024-03-13T21:22:05.711+09:00 INFO 24456 --- [HEARUS-SPRING] [ restartedMain] c.h.h.HearusSpringApplication : The following 1 profile is active: "private"
2024-03-13T21:22:05.793+09:00 INFO 24456 --- [HEARUS-SPRING] [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
...
2024-03-13T21:22:08.468+09:00 INFO 24456 --- [HEARUS-SPRING] [ restartedMain] c.h.h.HearusSpringApplication : Started HearusSpringApplication in 3.345 seconds (process running for 4.448)
Signup Logic을 위한 여러 객체들의 데이터 교환 정보는 위와 같다.
Controller와 Service, Handler는 DTO를 활용해 데이터를 교환하고
Handler에서 DTO를 Entitiy로 변환하여 Repository로 보내고 DB에 반영한다.
/data
/entitiy : Domain, DB에 쓰일 Column과 여러 Entity 연관관계 정의
하나의 Entitiy를 DB Table로 생각해도 무방함
/repository : Entitiy에 의해 생성된 DB에 접근하는 메소드를 사용하기 위한 인터페이스
Service와 DB를 연결하는 고리 역할 수행
DB에 적용하고자 하는 CRUD를 정의하는 영역
/impl
/dao DataAccessObject, DB에 접근하기 위한 객체
직접 DB에 접근하며 Persistent Layer, Service가 DB에 연결할 수 있게 해주는 역할
/impl
/dto DataTransferObject, 계층간 데이터 교환을 위한 객체
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
...
}
이러한 구조를 구현하기 위해 먼저 위와 같은 의존성을 build.gradle
에 추가한다.
// BaseEntitiy.java
package com.hearus.hearusspring.data.entitiy;
import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
@Getter
@Setter
@MappedSuperclass
public class BaseEntitiy {
// @MappedSuperClass
// 부모 클래스는 테이블과 매핑하지 않고 오로지 부모 클래스를 상속 받는 자식 클래스에게
// 부모 클래스가 가지는 Column만 매핑정보로 제공
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
이후 모든 Entitiy에 기본적으로 쓰일 BaseEntitiy
를 정의한다.
@MappedSuperclass
: 객체의 입장에서 공통 매핑 정보가 필요할 때 사용
// /enumType/GradeType.java
package com.hearus.hearusspring.data.enumType;
public enum GradeType {
FRESHMEN("FRESHMEN"),
SOPHOMORE("SOPHOMORE"),
JUNIOR("JUNIOR"),
SENIOR("SENIOR"),
LEAVE_ABSENCE("LEAVE_ABSENCE"),
GRADUATE("GRADUATE"),
POST_GRADUATE("POST_GRADUATE");
private final String key;
GradeType(String key) {
this.key = key;
}
}
// /enumType/OAuthType.java
package com.hearus.hearusspring.data.enumType;
public enum OAuthType {
KAKAO("KAKAO"),
GOOGLE("GOOGLE"),
NAVER("NAVER");
private final String key;
OAuthType(String key) {
this.key = key;
}
}
이후 추후 데이터 검증을 위한 enum
클래스들을 정의한다
// UserEntitiy.java
package com.hearus.hearusspring.data.entitiy;
import com.hearus.hearusspring.data.dto.UserDTO;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@Table(name = "user")
public class UserEntity extends BaseEntitiy{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
String id;
String name;
@Column(unique=true)
String email;
String password;
// OAuth인지의 여부 판단
boolean isOAuth;
// Oauth 종류
// KAKAO, GOOGLE, NAVER
String oauthType;
String school;
String major;
// 재학생 (1, 2, 3, 4), 휴학생, 졸업생
String grade;
// DB에는 일반적인 String으로 저장하고
// ','를 기준으로 ArrayList로 변환
String savedLectures;
String schedule;
String usePurpose;
public UserDTO toDTO(){
// ','를 기준으로 split
ArrayList<String> savedLecturesList = Arrays.stream(
savedLectures.split(","))
.map(String::trim)
.collect(Collectors.toCollection(ArrayList::new));
return UserDTO.builder()
.userId(id)
.userName(name)
.userEmail(email)
.userPassword(password)
.userIsOAuth(isOAuth)
.userOAuthType(oauthType)
.userSchool(school)
.userMajor(major)
.userGrade(grade)
.userSavedLectures(savedLecturesList)
.userSchedule(schedule)
.userUsePurpose(usePurpose)
.build();
}
}
DB와 소통하기 위한 User 정보인 UserEntitiy
객체를 위와 같이 선언한다.
이때 toDTO
메소드에서 String 형태로 저장된 UserEntitiy의 savedLecturesList
를 ArrayList 형태로 변환할 수 있는 코드를 추가한다.
// UserDTO.java
package com.hearus.hearusspring.data.dto;
import com.hearus.hearusspring.data.entitiy.UserEntity;
import com.hearus.hearusspring.data.enumType.OAuthType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.ArrayList;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class UserDTO {
@Id
@NotNull
String userId;
@NotNull
String userName;
@NotNull
@Email
String userEmail;
@NotNull
String userPassword;
@NotNull
boolean userIsOAuth;
String userOAuthType;
String userSchool;
String userMajor;
String userGrade;
ArrayList<String> userSavedLectures;
String userSchedule;
String userUsePurpose;
public UserEntity toEntitiy(){
String stringLecture = String.join(",", userSavedLectures);
return UserEntity.builder()
.id(userId)
.name(userName)
.email(userEmail)
.password(userPassword)
.isOAuth(userIsOAuth)
.oauthType(userOAuthType)
.school(userSchool)
.major(userMajor)
.grade(userGrade)
.savedLectures(stringLecture)
.schedule(userSchedule)
.usePurpose(userUsePurpose)
.build();
}
}
이후 UserDTO 객체를 추가한다.
// UserDAO.java
package com.hearus.hearusspring.data.dao;
import com.hearus.hearusspring.data.entitiy.UserEntity;
public interface UserDAO {
UserEntity userLogin(UserEntity user);
boolean userSignup(UserEntity user);
}
// UserDAOImpl.java
package com.hearus.hearusspring.data.dao.impl;
import com.hearus.hearusspring.data.dao.UserDAO;
import com.hearus.hearusspring.data.entitiy.UserEntity;
import com.hearus.hearusspring.data.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserDAOImpl implements UserDAO {
UserRepository userRepository;
private final Logger LOGGER = LoggerFactory.getLogger(UserDAOImpl.class);
// Todo : Create Login Function
@Override
public UserEntity userLogin(UserEntity user) {
return null;
}
@Override
public boolean userSignup(UserEntity user) {
LOGGER.info("[UserDAO]-[userSignup] UserEntitiy 저장 : {}", user.getEmail());
// 이메일 중복 여부 확인
if(userRepository.existsByEmail(user.getEmail())){
LOGGER.info("[UserDAO]-[userSignup] 회원 이메일 중복 : {}", user.getEmail());
return false;
}
userRepository.save(user);
LOGGER.info("[UserDAO]-[userSignup] UserEntitiy 저장 성공 : {}", user.getEmail());
return true;
}
}
UserDAO
인터페이스를 생성하고 현재는 userRepository
를 통해 Signup을 구현하는 코드만 추가한다.
// UserRepository.java
package com.hearus.hearusspring.data.repository;
import com.hearus.hearusspring.data.entitiy.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, String> {
// Find By
UserEntity findFirstById(String id);
UserEntity findFirstByEmail(String email);
UserEntity findFirstByEmailAndPassword(String email, String password);
// Exist By
boolean existsByEmail(String email);
}
UserRepository
에서는 이메일 중복 여부를 확인하기 위해 위와 같은 쿼리만 추가한다.
// UserHandler.java
package com.hearus.hearusspring.data.handler;
import com.hearus.hearusspring.data.dto.UserDTO;
public interface UserHandler {
boolean signupUserEntitiy(UserDTO user);
}
// UserHandlerImpl.java
package com.hearus.hearusspring.data.handler.impl;
import com.hearus.hearusspring.data.dao.UserDAO;
import com.hearus.hearusspring.data.dto.UserDTO;
import com.hearus.hearusspring.data.entitiy.UserEntity;
import com.hearus.hearusspring.data.handler.UserHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserHandlerImpl implements UserHandler {
UserDAO userDAO;
private final Logger LOGGER = LoggerFactory.getLogger(UserHandlerImpl.class);
@Override
public boolean signupUserEntitiy(UserDTO user) {
LOGGER.info("[UserHandler]-[signupUserEntitiy] UserDAO로 UserEntitiy 회원가입 요청 : {}", user.getUserEmail());
UserEntity userEntity = user.toEntitiy();
return userDAO.userSignup(userEntity);
}
}
UserHandler
에서는 UserDTO
를 받아 UserEntitiy
로 변환하여 UserDAO
로 요청을 보낸다.
// UserService.java
package com.hearus.hearusspring.service;
import com.hearus.hearusspring.data.dto.UserDTO;
public interface UserService {
boolean userSignup(UserDTO user);
}
// UserServiceImpl
package com.hearus.hearusspring.service.impl;
import com.hearus.hearusspring.data.dto.UserDTO;
import com.hearus.hearusspring.data.handler.UserHandler;
import com.hearus.hearusspring.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class UserServiceImpl implements UserService {
UserHandler userHandler;
private final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
@Override
public boolean userSignup(UserDTO user) {
LOGGER.info("[UserService]-[userSignup] UserHandler로 회원가입 요청 : {}", user.getUserEmail());
userHandler.signupUserEntitiy(user);
return true;
}
}
UserService
에서는 UserController
로부터 UserDTO
객체를 전달받아 UserHandler
로 요청한다.
// /common/CommonResponse.java
package com.hearus.hearusspring.common;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.HttpStatus;
@Getter
@Setter
public class CommonResponse {
private boolean isSuccess;
HttpStatus resCode;
private String msg;
public CommonResponse(boolean isSuccess, String msg) {
this.isSuccess = isSuccess;
this.msg = msg;
}
public CommonResponse(boolean isSuccess, HttpStatus resCode, String msg) {
this.isSuccess = isSuccess;
this.resCode = resCode;
this.msg = msg;
}
}
Controller를 구현하기 이전 하나의 요청에 대한 Response
를 효율적으로 관리하기 위해 위와 같이 CommonResponse
객체를 구현하고 Service, Handler, DAO의 메소드의 return 타입을 CommonResponse
으로 설정하여 Client로의 HTTP Repsonse를 더 효과적으로 구현할 수 있게 하였다.
// UserDAO.java
public class UserDAOImpl implements UserDAO {
UserRepository userRepository;
private final Logger LOGGER = LoggerFactory.getLogger(UserDAOImpl.class);
// TODO : Create Login Function
@Override
public UserEntity userLogin(UserEntity user) {
return null;
}
@Override
public CommonResponse userSignup(UserEntity user) {
LOGGER.info("[UserDAO]-[userSignup] UserEntitiy 저장 : {}", user.getEmail());
// 이메일 중복 여부 확인
if(userRepository.existsByEmail(user.getEmail())){
LOGGER.info("[UserDAO]-[userSignup] 회원 이메일 중복 : {}", user.getEmail());
return new CommonResponse(false, HttpStatus.CONFLICT,"User Already Exists");
}
userRepository.save(user);
LOGGER.info("[UserDAO]-[userSignup] UserEntitiy 저장 성공 : {}", user.getEmail());
return new CommonResponse(true,HttpStatus.CREATED,"Signup Success");
}
}
위와 같이 UserDAO
에서 회원 이메일 중복으로 적용에 실패했을 경우 CommonResponse
객체에 정보를 담아 Controller까지 전달할 수 있도록 구조를 설계하였다.
package com.hearus.hearusspring.controller;
import com.hearus.hearusspring.common.CommonResponse;
import com.hearus.hearusspring.data.dto.UserDTO;
import com.hearus.hearusspring.service.UserService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
public class UserAuthController {
private final Logger LOGGER = LoggerFactory.getLogger(UserAuthController.class);
private final UserService userService;
private CommonResponse response;
@Autowired
public UserAuthController(UserService userService) {
this.userService = userService;
}
@PostMapping(value="/signup")
public ResponseEntity<CommonResponse> signupUser(@Valid @RequestBody UserDTO userDTO){
LOGGER.info("[UserAuthController]-[signupUser] API Call");
// 요구되는 데이터 존재 여부 검증
if(userDTO.getUserName().isEmpty() || userDTO.getUserEmail().isEmpty() || userDTO.getUserPassword().isEmpty()){
LOGGER.info("[UserAuthController]-[signupUser] Failed : Empty Variables");
response = new CommonResponse(false,HttpStatus.BAD_REQUEST,"Empty Variables");
return ResponseEntity.status(response.getStatus()).body(response);
}
// UserService로 요청받은 UserDTO 회원가입 요청
userService.userSignup(userDTO);
LOGGER.info("[UserAuthController]-[signupUser] Success");
response = new CommonResponse(true,HttpStatus.CREATED,"Signup Success");
return ResponseEntity.status(response.getStatus()).body(response);
}
}
최종적으로 위와 같이 UserController
를 구현하여 테스트를 진행하였다.
implementation group: 'org.javassist', name: 'javassist', version: '3.15.0-GA'
위 오류는 JDBC관련 오류로 위와 같이 javassist
의존성을 추가하고
spring.datasource.url=jdbc:mariadb://127.0.0.1:3308/hearus_test
datasource.url
를 다시 점검하여 해결할 수 있다.
2024-03-15T22:29:40.020+09:00 INFO 29780 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.controller.UserAuthController : [UserAuthController]-[signupUser] API Call
2024-03-15T22:29:40.020+09:00 INFO 29780 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.service.impl.UserServiceImpl : [UserService]-[userSignup] UserHandler로 회원가입 요청 : judemin@konkuk.ac.kr
2024-03-15T22:29:40.021+09:00 ERROR 29780 --- [HEARUS-SPRING] [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "com.hearus.hearusspring.data.handler.UserHandler.signupUserEntitiy(com.hearus.hearusspring.data.dto.UserDTO)" because "this.userHandler" is null] with root cause
java.lang.NullPointerException: Cannot invoke "com.hearus.hearusspring.data.handler.UserHandler.signupUserEntitiy(com.hearus.hearusspring.data.dto.UserDTO)" because "this.userHandler" is null
해당 에러는 @Autowired
되어야 하는 객체들이 주입되지 않아 발생하는 오류로 각 인터페이스, 그리고 해당 인터페이스를 활용하는 객체의 생성자에 @Autowired
해주어 해결할 수 있다.
@Service
@Transactional
public class UserHandlerImpl implements UserHandler {
UserDAO userDAO;
...
@Service
: 비즈니스 로직을 처리하는 서비스(Service) 클래스에 적용
@Transactional
: 해당하는 메서드를 실행할 때 스프링은 트랜잭션을 시작하고, 메서드가 정상적으로 종료되면 트랜잭션을 commit하고, 예외가 발생하면 트랜잭션을 rollback
@Autowired
: 필요한 의존 객체(Bean)의 Type에 대당하는 Bean을 찾아서 주입
위와 같이 정상적으로 회원가입이 동작하고
이메일 중복시 CommonResponse
객체를 통해 결과가 잘 전달되는 것을 볼 수 있다.
@EnableJpaAuditing
@SpringBootApplication
public class HearusSpringApplication {
public static void main(String[] args) {
SpringApplication.run(HearusSpringApplication.class, args);
}
}
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntitiy {
...
EnableJpaAuditing
을 통해 Auditing을 활성화해주면
위와 같이 정상적으로 BaseEntitiy
의 created_at
값이 들어가는 것을 볼 수 있다.
2024-03-15T22:59:53.527+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.controller.UserAuthController : [UserAuthController]-[signupUser] API Call
2024-03-15T22:59:53.527+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.service.impl.UserServiceImpl : [UserService]-[userSignup] UserHandler로 회원가입 요청 : judemin@konkuk.ac.kr
2024-03-15T22:59:53.528+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.data.handler.impl.UserHandlerImpl : [UserHandler]-[signupUserEntitiy] UserDAO로 UserEntitiy 회원가입 요청 : judemin@konkuk.ac.kr
2024-03-15T22:59:53.529+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.data.dao.impl.UserDAOImpl : [UserDAO]-[userSignup] UserEntitiy 저장 : judemin@konkuk.ac.kr
2024-03-15T22:59:53.624+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.data.dao.impl.UserDAOImpl : [UserDAO]-[userSignup] UserEntitiy 저장 성공 : judemin@konkuk.ac.kr
2024-03-15T22:59:53.654+09:00 INFO 2196 --- [HEARUS-SPRING] [nio-8080-exec-3] c.h.h.controller.UserAuthController : [UserAuthController]-[signupUser] Success