From 021aaba373880e79c37dd1b305f1adbeaeaa4213 Mon Sep 17 00:00:00 2001 From: ybchar Date: Sat, 6 Apr 2024 02:42:36 +0900 Subject: [PATCH 1/3] chore: RestControllerAdvice --- .../common/response/GlobalResponse.java | 15 ++ .../common/response/GlobalResponseAdvice.java | 41 +++++ .../daybook/global/error/ErrorResponse.java | 8 + .../global/error/GlobalExceptionHandler.java | 147 ++++++++++++++++++ .../error/exception/CustomException.java | 14 ++ .../global/error/exception/ErrorCode.java | 21 +++ 6 files changed, 246 insertions(+) create mode 100644 src/main/java/com/unit/daybook/global/common/response/GlobalResponse.java create mode 100644 src/main/java/com/unit/daybook/global/common/response/GlobalResponseAdvice.java create mode 100644 src/main/java/com/unit/daybook/global/error/ErrorResponse.java create mode 100644 src/main/java/com/unit/daybook/global/error/GlobalExceptionHandler.java create mode 100644 src/main/java/com/unit/daybook/global/error/exception/CustomException.java create mode 100644 src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java diff --git a/src/main/java/com/unit/daybook/global/common/response/GlobalResponse.java b/src/main/java/com/unit/daybook/global/common/response/GlobalResponse.java new file mode 100644 index 0000000..7385e80 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/common/response/GlobalResponse.java @@ -0,0 +1,15 @@ +package com.unit.daybook.global.common.response; + +import java.time.LocalDateTime; + +import com.unit.daybook.global.error.ErrorResponse; + +public record GlobalResponse(int status, Object data, LocalDateTime timestamp) { + public static GlobalResponse success(int status, Object data) { + return new GlobalResponse(status, data, LocalDateTime.now()); + } + + public static GlobalResponse fail(int status, ErrorResponse errorResponse) { + return new GlobalResponse(status, errorResponse, LocalDateTime.now()); + } +} diff --git a/src/main/java/com/unit/daybook/global/common/response/GlobalResponseAdvice.java b/src/main/java/com/unit/daybook/global/common/response/GlobalResponseAdvice.java new file mode 100644 index 0000000..0a63558 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/common/response/GlobalResponseAdvice.java @@ -0,0 +1,41 @@ +package com.unit.daybook.global.common.response; + +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import jakarta.servlet.http.HttpServletResponse; + +@RestControllerAdvice(basePackages = "com.unit.daybook") +public class GlobalResponseAdvice implements ResponseBodyAdvice { + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return true; + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response) { + HttpServletResponse servletResponse = + ((ServletServerHttpResponse) response).getServletResponse(); + int status = servletResponse.getStatus(); + HttpStatus resolve = HttpStatus.resolve(status); + if (resolve == null || body instanceof String) { + return body; + } + if (resolve.is2xxSuccessful()) { + return GlobalResponse.success(status, body); + } + return body; + } +} diff --git a/src/main/java/com/unit/daybook/global/error/ErrorResponse.java b/src/main/java/com/unit/daybook/global/error/ErrorResponse.java new file mode 100644 index 0000000..d199a3a --- /dev/null +++ b/src/main/java/com/unit/daybook/global/error/ErrorResponse.java @@ -0,0 +1,8 @@ +package com.unit.daybook.global.error; + +public record ErrorResponse(String errorClassName, String message) { + + public static ErrorResponse of(String errorClassName, String message) { + return new ErrorResponse(errorClassName, message); + } +} diff --git a/src/main/java/com/unit/daybook/global/error/GlobalExceptionHandler.java b/src/main/java/com/unit/daybook/global/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..2038d06 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/error/GlobalExceptionHandler.java @@ -0,0 +1,147 @@ +package com.unit.daybook.global.error; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import com.unit.daybook.global.common.response.GlobalResponse; +import com.unit.daybook.global.error.exception.CustomException; +import com.unit.daybook.global.error.exception.ErrorCode; + +import jakarta.validation.ConstraintViolationException; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, + Object body, + HttpHeaders headers, + HttpStatusCode statusCode, + WebRequest request) { + ErrorResponse errorResponse = + ErrorResponse.of(ex.getClass().getSimpleName(), ex.getMessage()); + return super.handleExceptionInternal(ex, errorResponse, headers, statusCode, request); + } + + /** + * javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다. HttpMessageConverter 에서 등록한 + * HttpMessageConverter binding 못할경우 발생 주로 @RequestBody, @RequestPart 어노테이션에서 발생 + */ + @SneakyThrows + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException e, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + log.error("MethodArgumentNotValidException : {}", e.getMessage(), e); + String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + final ErrorResponse errorResponse = + ErrorResponse.of(e.getClass().getSimpleName(), errorMessage); + GlobalResponse response = GlobalResponse.fail(status.value(), errorResponse); + return ResponseEntity.status(status).body(response); + } + + /** Request Param Validation 예외 처리 */ + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException( + ConstraintViolationException e) { + log.error("ConstraintViolationException : {}", e.getMessage(), e); + + Map bindingErrors = new HashMap<>(); + e.getConstraintViolations() + .forEach( + constraintViolation -> { + List propertyPath = + List.of( + constraintViolation + .getPropertyPath() + .toString() + .split("\\.")); + String path = + propertyPath.stream() + .skip(propertyPath.size() - 1L) + .findFirst() + .orElse(null); + bindingErrors.put(path, constraintViolation.getMessage()); + }); + + final ErrorResponse errorResponse = + ErrorResponse.of(e.getClass().getSimpleName(), bindingErrors.toString()); + final GlobalResponse response = + GlobalResponse.fail(HttpStatus.BAD_REQUEST.value(), errorResponse); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + + /** PathVariable, RequestParam, RequestHeader, RequestBody 에서 타입이 일치하지 않을 경우 발생 */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + protected ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException e) { + log.error("MethodArgumentTypeMismatchException : {}", e.getMessage(), e); + final ErrorCode errorCode = ErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorResponse errorResponse = + ErrorResponse.of(e.getClass().getSimpleName(), errorCode.getMessage()); + final GlobalResponse response = + GlobalResponse.fail(errorCode.getStatus().value(), errorResponse); + return ResponseEntity.status(errorCode.getStatus()).body(response); + } + + /** 지원하지 않은 HTTP method 호출 할 경우 발생 */ + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException e, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + log.error("HttpRequestMethodNotSupportedException : {}", e.getMessage(), e); + final ErrorCode errorCode = ErrorCode.METHOD_NOT_ALLOWED; + final ErrorResponse errorResponse = + ErrorResponse.of(e.getClass().getSimpleName(), errorCode.getMessage()); + final GlobalResponse response = + GlobalResponse.fail(errorCode.getStatus().value(), errorResponse); + return ResponseEntity.status(errorCode.getStatus()).body(response); + } + + /** CustomException 예외 처리 */ + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException e) { + log.error("CustomException : {}", e.getMessage(), e); + final ErrorCode errorCode = e.getErrorCode(); + final ErrorResponse errorResponse = + ErrorResponse.of(errorCode.name(), errorCode.getMessage()); + final GlobalResponse response = + GlobalResponse.fail(errorCode.getStatus().value(), errorResponse); + return ResponseEntity.status(errorCode.getStatus()).body(response); + } + + /** 500번대 에러 처리 */ + @ExceptionHandler(Exception.class) + protected ResponseEntity handleException(Exception e) { + log.error("Internal Server Error : {}", e.getMessage(), e); + final ErrorCode internalServerError = ErrorCode.INTERNAL_SERVER_ERROR; + final ErrorResponse errorResponse = + ErrorResponse.of(e.getClass().getSimpleName(), internalServerError.getMessage()); + final GlobalResponse response = + GlobalResponse.fail(internalServerError.getStatus().value(), errorResponse); + return ResponseEntity.status(internalServerError.getStatus()).body(response); + } +} diff --git a/src/main/java/com/unit/daybook/global/error/exception/CustomException.java b/src/main/java/com/unit/daybook/global/error/exception/CustomException.java new file mode 100644 index 0000000..479bb72 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/error/exception/CustomException.java @@ -0,0 +1,14 @@ +package com.unit.daybook.global.error.exception; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java new file mode 100644 index 0000000..f674f4a --- /dev/null +++ b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java @@ -0,0 +1,21 @@ +package com.unit.daybook.global.error.exception; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ErrorCode { + SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "Sample Error Message"), + + // Common + METHOD_ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "요청 한 값 타입이 잘못되어 binding에 실패하였습니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP method 입니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), + + ; + private final HttpStatus status; + private final String message; +} From d813239917abc9a376243e4a4d62a3e4eab7edfe Mon Sep 17 00:00:00 2001 From: ybchar Date: Sat, 6 Apr 2024 03:01:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EA=B5=AC=ED=98=84(1=EC=B0=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../daybook/domain/auth/controller/.gitkeep | 0 .../unit/daybook/domain/auth/service/.gitkeep | 0 .../domain/common/model/BaseTimeEntity.java | 26 +++++++ .../daybook/domain/member/controller/.gitkeep | 0 .../daybook/domain/member/domain/Member.java | 74 +++++++++++++++++++ .../domain/member/domain/MemberRole.java | 13 ++++ .../domain/member/domain/MemberStatus.java | 14 ++++ .../domain/member/domain/OauthInfo.java | 33 +++++++++ .../domain/member/dto/request/.gitkeep | 0 .../domain/member/dto/response/.gitkeep | 0 .../member/repository/MemberRepository.java | 8 ++ .../repository/MemberRepositoryCustom.java | 4 + .../repository/MemberRepositoryImpl.java | 10 +++ 13 files changed, 182 insertions(+) create mode 100644 src/main/java/com/unit/daybook/domain/auth/controller/.gitkeep create mode 100644 src/main/java/com/unit/daybook/domain/auth/service/.gitkeep create mode 100644 src/main/java/com/unit/daybook/domain/common/model/BaseTimeEntity.java create mode 100644 src/main/java/com/unit/daybook/domain/member/controller/.gitkeep create mode 100644 src/main/java/com/unit/daybook/domain/member/domain/Member.java create mode 100644 src/main/java/com/unit/daybook/domain/member/domain/MemberRole.java create mode 100644 src/main/java/com/unit/daybook/domain/member/domain/MemberStatus.java create mode 100644 src/main/java/com/unit/daybook/domain/member/domain/OauthInfo.java create mode 100644 src/main/java/com/unit/daybook/domain/member/dto/request/.gitkeep create mode 100644 src/main/java/com/unit/daybook/domain/member/dto/response/.gitkeep create mode 100644 src/main/java/com/unit/daybook/domain/member/repository/MemberRepository.java create mode 100644 src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryCustom.java create mode 100644 src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryImpl.java diff --git a/src/main/java/com/unit/daybook/domain/auth/controller/.gitkeep b/src/main/java/com/unit/daybook/domain/auth/controller/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/unit/daybook/domain/auth/service/.gitkeep b/src/main/java/com/unit/daybook/domain/auth/service/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/unit/daybook/domain/common/model/BaseTimeEntity.java b/src/main/java/com/unit/daybook/domain/common/model/BaseTimeEntity.java new file mode 100644 index 0000000..5e2732a --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/common/model/BaseTimeEntity.java @@ -0,0 +1,26 @@ +package com.unit.daybook.domain.common.model; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTimeEntity { + + @Column(updatable = false) + @CreatedDate + private LocalDateTime createdAt; + + @Column @LastModifiedDate + private LocalDateTime updatedAt; +} + diff --git a/src/main/java/com/unit/daybook/domain/member/controller/.gitkeep b/src/main/java/com/unit/daybook/domain/member/controller/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/unit/daybook/domain/member/domain/Member.java b/src/main/java/com/unit/daybook/domain/member/domain/Member.java new file mode 100644 index 0000000..9e843d8 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/domain/Member.java @@ -0,0 +1,74 @@ +package com.unit.daybook.domain.member.domain; + +import java.time.LocalDateTime; + +import com.unit.daybook.domain.common.model.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") + private Long id; + + private String nickname; + + @Embedded + private OauthInfo oauthInfo; + + @Enumerated(EnumType.STRING) + private MemberStatus status; + + @Enumerated(EnumType.STRING) + private MemberRole role; + + private LocalDateTime lastLoginAt; + + + @Builder(access = AccessLevel.PRIVATE) + private Member( + String nickname, + OauthInfo oauthInfo, + MemberStatus status, + MemberRole role, + LocalDateTime lastLoginAt) { + this.nickname = nickname; + this.oauthInfo = oauthInfo; + this.status = status; + this.role = role; + this.lastLoginAt = lastLoginAt; + } + + public static Member createNormalMember(OauthInfo oauthInfo, String nickname) { + return Member.builder() + .nickname(nickname) + .oauthInfo(oauthInfo) + .status(MemberStatus.NORMAL) + .role(MemberRole.USER) + .build(); + } + + public void updateLastLoginAt() { + this.lastLoginAt = LocalDateTime.now(); + } + + public void updateNickname() { + this.nickname = nickname; + } +} diff --git a/src/main/java/com/unit/daybook/domain/member/domain/MemberRole.java b/src/main/java/com/unit/daybook/domain/member/domain/MemberRole.java new file mode 100644 index 0000000..66eb31c --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/domain/MemberRole.java @@ -0,0 +1,13 @@ +package com.unit.daybook.domain.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MemberRole { + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String value; +} \ No newline at end of file diff --git a/src/main/java/com/unit/daybook/domain/member/domain/MemberStatus.java b/src/main/java/com/unit/daybook/domain/member/domain/MemberStatus.java new file mode 100644 index 0000000..b80bb6c --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/domain/MemberStatus.java @@ -0,0 +1,14 @@ +package com.unit.daybook.domain.member.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MemberStatus { + NORMAL("NORMAL"), + DELETED("DELETED"), + FORBIDDEN("FORBIDDEN"); + + private final String value; +} diff --git a/src/main/java/com/unit/daybook/domain/member/domain/OauthInfo.java b/src/main/java/com/unit/daybook/domain/member/domain/OauthInfo.java new file mode 100644 index 0000000..7e0acc7 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/domain/OauthInfo.java @@ -0,0 +1,33 @@ +package com.unit.daybook.domain.member.domain; + +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class OauthInfo { + + private String oauthId; + private String oauthProvider; + private String oauthEmail; + + @Builder(access = AccessLevel.PRIVATE) + private OauthInfo(String oauthId, String oauthProvider, String oauthEmail) { + this.oauthId = oauthId; + this.oauthProvider = oauthProvider; + this.oauthEmail = oauthEmail; + } + + public static OauthInfo createOauthInfo( + String oauthId, String oauthProvider, String oauthEmail) { + return OauthInfo.builder() + .oauthId(oauthId) + .oauthProvider(oauthProvider) + .oauthEmail(oauthEmail) + .build(); + } +} diff --git a/src/main/java/com/unit/daybook/domain/member/dto/request/.gitkeep b/src/main/java/com/unit/daybook/domain/member/dto/request/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/unit/daybook/domain/member/dto/response/.gitkeep b/src/main/java/com/unit/daybook/domain/member/dto/response/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/unit/daybook/domain/member/repository/MemberRepository.java b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepository.java new file mode 100644 index 0000000..34485cd --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepository.java @@ -0,0 +1,8 @@ +package com.unit.daybook.domain.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.unit.daybook.domain.member.domain.Member; + +public interface MemberRepository extends JpaRepository, MemberRepositoryCustom { +} diff --git a/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryCustom.java new file mode 100644 index 0000000..fec994c --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryCustom.java @@ -0,0 +1,4 @@ +package com.unit.daybook.domain.member.repository; + +public interface MemberRepositoryCustom { +} diff --git a/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryImpl.java new file mode 100644 index 0000000..794774a --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/repository/MemberRepositoryImpl.java @@ -0,0 +1,10 @@ +package com.unit.daybook.domain.member.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class MemberRepositoryImpl implements MemberRepositoryCustom { +} From 6f535e4ed9c31d5869aa744adff27dafff485118 Mon Sep 17 00:00:00 2001 From: ybchar Date: Sat, 6 Apr 2024 03:06:34 +0900 Subject: [PATCH 3/3] chore: querydsl config --- .../daybook/global/config/QuerydslConfig.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/com/unit/daybook/global/config/QuerydslConfig.java diff --git a/src/main/java/com/unit/daybook/global/config/QuerydslConfig.java b/src/main/java/com/unit/daybook/global/config/QuerydslConfig.java new file mode 100644 index 0000000..4259f62 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/config/QuerydslConfig.java @@ -0,0 +1,22 @@ +package com.unit.daybook.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.querydsl.jpa.JPQLTemplates; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@Configuration +public class QuerydslConfig { + + @PersistenceContext + public EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); + } +}