spring 버전 변경
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.3.2</version>
|
<version>2.7.18</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.spring</groupId>
|
<groupId>com.spring</groupId>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<url/>
|
<url/>
|
||||||
</scm>
|
</scm>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
<java.version>11</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.spring.common.util;
|
|
||||||
|
|
||||||
import org.springframework.web.context.ContextLoader;
|
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class ApplicationUtil {
|
|
||||||
|
|
||||||
public static ServletRequestAttributes getServletRequestAttributes(){
|
|
||||||
return (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
|
|
||||||
}
|
|
||||||
//Request 객체 얻기
|
|
||||||
public static HttpServletRequest getRequest(){
|
|
||||||
return getServletRequestAttributes().getRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set Request Attribute
|
|
||||||
public static void setRequestAttributes(String key, Object obj){
|
|
||||||
getServletRequestAttributes().setAttribute(key, obj, RequestAttributes.SCOPE_REQUEST);
|
|
||||||
}
|
|
||||||
//Get Request Attribute
|
|
||||||
public static Object getRequestAttributes(String key){
|
|
||||||
return getServletRequestAttributes().getAttribute(key, RequestAttributes.SCOPE_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Session 객체 얻기
|
|
||||||
public static HttpSession getSession(){
|
|
||||||
return getRequest().getSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set Session Attributes
|
|
||||||
public static void setSessionAttributes(String key, Object obj){
|
|
||||||
getServletRequestAttributes().setAttribute(key, obj, RequestAttributes.SCOPE_SESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get Session Attributes
|
|
||||||
public static Object getSessionAttributes(String key){
|
|
||||||
return getServletRequestAttributes().getAttribute(key, RequestAttributes.SCOPE_SESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
//HttpServletResponse 객체 얻기
|
|
||||||
public static HttpServletResponse getResponse(){
|
|
||||||
return getServletRequestAttributes().getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
//beanName을 통해서 Bean을 얻을 수 있다.
|
|
||||||
public static Object getBean(String beanName){
|
|
||||||
var context = ContextLoader.getCurrentWebApplicationContext();
|
|
||||||
if (context == null) {
|
|
||||||
throw new IllegalStateException("WebApplicationContext를 찾을 수 없습니다.");
|
|
||||||
}
|
|
||||||
return context.getBean(beanName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.spring.common.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring ApplicationContext에 접근하기 위한 유틸리티 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 ApplicationContextAware를 구현하여 ApplicationContext를 저장하고,
|
||||||
|
* 애플리케이션 전역에서 Spring 빈에 접근할 수 있는 정적 메소드를 제공합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ContextUtils implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final AtomicReference<ApplicationContext> applicationContext = new AtomicReference<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring에 의해 호출되어 ApplicationContext를 설정합니다.
|
||||||
|
*
|
||||||
|
* @param context 설정할 ApplicationContext
|
||||||
|
* @throws BeansException 빈 예외 발생 시
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(@NonNull ApplicationContext context) throws BeansException {
|
||||||
|
applicationContext.set(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정된 이름과 타입의 빈을 반환합니다.
|
||||||
|
*
|
||||||
|
* @param <T> 반환될 빈의 타입
|
||||||
|
* @param beanName 찾을 빈의 이름
|
||||||
|
* @param clazz 반환될 빈의 클래스
|
||||||
|
* @return 지정된 이름과 타입의 빈
|
||||||
|
* @throws IllegalStateException ApplicationContext가 설정되지 않은 경우
|
||||||
|
*/
|
||||||
|
public static <T> T getBean(String beanName, Class<T> clazz) {
|
||||||
|
ApplicationContext context = applicationContext.get();
|
||||||
|
if (context == null) {
|
||||||
|
throw new IllegalStateException("ApplicationContext가 설정되지 않았습니다.");
|
||||||
|
}
|
||||||
|
return context.getBean(beanName, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정된 타입의 빈을 반환합니다.
|
||||||
|
*
|
||||||
|
* @param <T> 반환될 빈의 타입
|
||||||
|
* @param clazz 반환될 빈의 클래스
|
||||||
|
* @return 지정된 타입의 빈
|
||||||
|
* @throws IllegalStateException ApplicationContext가 설정되지 않은 경우
|
||||||
|
*/
|
||||||
|
public static <T> T getBean(Class<T> clazz) {
|
||||||
|
ApplicationContext context = applicationContext.get();
|
||||||
|
if (context == null) {
|
||||||
|
throw new IllegalStateException("ApplicationContext가 설정되지 않았습니다.");
|
||||||
|
}
|
||||||
|
return context.getBean(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.spring.common.util;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 서블릿 관련 유틸리티 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>현재 요청의 HttpServletRequest, HttpServletResponse, HttpSession 등에 접근하는 메서드를 제공합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class ServletUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 ServletRequestAttributes를 Optional로 반환합니다.
|
||||||
|
*
|
||||||
|
* @return ServletRequestAttributes를 포함한 Optional 객체
|
||||||
|
*/
|
||||||
|
private static Optional<ServletRequestAttributes> getAttributes() {
|
||||||
|
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
|
||||||
|
.filter(ServletRequestAttributes.class::isInstance)
|
||||||
|
.map(ServletRequestAttributes.class::cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServletRequestAttributes에서 특정 속성을 추출합니다.
|
||||||
|
*
|
||||||
|
* @param getter ServletRequestAttributes에서 원하는 속성을 추출하는 함수
|
||||||
|
* @param <T> 반환될 속성의 타입
|
||||||
|
* @return 추출된 속성 또는 null
|
||||||
|
*/
|
||||||
|
private static <T> T getAttribute(Function<ServletRequestAttributes, T> getter) {
|
||||||
|
return getAttributes().map(getter).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 HttpServletRequest를 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 현재 HttpServletRequest 또는 null
|
||||||
|
*/
|
||||||
|
public static HttpServletRequest getRequest() {
|
||||||
|
return getAttribute(ServletRequestAttributes::getRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 속성을 설정합니다.
|
||||||
|
*
|
||||||
|
* @param key 속성의 키
|
||||||
|
* @param obj 설정할 속성 값
|
||||||
|
*/
|
||||||
|
public static void setRequestAttribute(String key, Object obj) {
|
||||||
|
getAttributes().ifPresent(attr -> attr.setAttribute(key, obj, RequestAttributes.SCOPE_REQUEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 속성을 반환합니다.
|
||||||
|
*
|
||||||
|
* @param key 속성의 키
|
||||||
|
* @return 요청 속성 값 또는 null
|
||||||
|
*/
|
||||||
|
public static Object getRequestAttribute(String key) {
|
||||||
|
return getAttribute(attr -> attr.getAttribute(key, RequestAttributes.SCOPE_REQUEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 HttpSession을 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 현재 HttpSession 또는 null
|
||||||
|
*/
|
||||||
|
public static HttpSession getSession() {
|
||||||
|
return Optional.ofNullable(getRequest()).map(HttpServletRequest::getSession).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 세션의 속성을 설정합니다.
|
||||||
|
*
|
||||||
|
* @param key 속성의 키
|
||||||
|
* @param obj 설정할 속성 값
|
||||||
|
*/
|
||||||
|
public static void setSessionAttribute(String key, Object obj) {
|
||||||
|
getAttributes().ifPresent(attr -> attr.setAttribute(key, obj, RequestAttributes.SCOPE_SESSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 세션의 속성을 반환합니다.
|
||||||
|
*
|
||||||
|
* @param key 속성의 키
|
||||||
|
* @return 세션 속성 값 또는 null
|
||||||
|
*/
|
||||||
|
public static Object getSessionAttribute(String key) {
|
||||||
|
return getAttribute(attr -> attr.getAttribute(key, RequestAttributes.SCOPE_SESSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 요청의 HttpServletResponse를 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 현재 HttpServletResponse 또는 null
|
||||||
|
*/
|
||||||
|
public static HttpServletResponse getResponse() {
|
||||||
|
return getAttribute(ServletRequestAttributes::getResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,15 +2,14 @@ package com.spring.domain.batch.entity;
|
|||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
// @Entity
|
// @Entity
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.spring.domain.batch.entity;
|
package com.spring.domain.batch.entity;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.OneToOne;
|
||||||
import jakarta.persistence.OneToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
// @Entity
|
// @Entity
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ package com.spring.domain.batch.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ public class BatchJobExecutionParams {
|
|||||||
@Embeddable
|
@Embeddable
|
||||||
@Getter
|
@Getter
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class BatchJobExecutionParamsId implements Serializable {
|
public static class BatchJobExecutionParamsId implements Serializable {
|
||||||
@Column(name = "JOB_EXECUTION_ID")
|
@Column(name = "JOB_EXECUTION_ID")
|
||||||
private Long jobExecutionId;
|
private Long jobExecutionId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.spring.domain.batch.entity;
|
package com.spring.domain.batch.entity;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.UniqueConstraint;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
// @Entity
|
// @Entity
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package com.spring.domain.batch.entity;
|
|||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
// @Entity
|
// @Entity
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.spring.domain.batch.entity;
|
package com.spring.domain.batch.entity;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.OneToOne;
|
||||||
import jakarta.persistence.OneToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
// @Entity
|
// @Entity
|
||||||
|
|||||||
@@ -3,45 +3,40 @@ package com.spring.domain.email.batch;
|
|||||||
import org.springframework.batch.core.Job;
|
import org.springframework.batch.core.Job;
|
||||||
import org.springframework.batch.core.Step;
|
import org.springframework.batch.core.Step;
|
||||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
|
||||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
import org.springframework.batch.repeat.RepeatStatus;
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
|
|
||||||
import com.spring.infra.quartz.QuartzJob;
|
import com.spring.infra.quartz.QuartzJob;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class EmailSendBatch {
|
public class EmailSendBatch {
|
||||||
|
|
||||||
private final JobRepository jobRepository;
|
@QuartzJob(group = "EMAIL", name = "emailSendJob", cronExpression = "*/3 * * * * ?")
|
||||||
private final PlatformTransactionManager transactionManager;
|
// @JobScope
|
||||||
|
|
||||||
@QuartzJob(name = "emailSendJob", cronExpression = "*/3 * * * * ?")
|
|
||||||
@Bean(name = "emailSendJob")
|
@Bean(name = "emailSendJob")
|
||||||
Job emailSendJob(Step emailSendStep) {
|
Job emailSendJob(Step emailSendStep) {
|
||||||
log.info(">>> emailSendJob");
|
log.info(">>> emailSendJob");
|
||||||
return new JobBuilder("emailSendJob", jobRepository)
|
return new JobBuilder("emailSendJob")
|
||||||
.start(emailSendStep)
|
.start(emailSendStep)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @StepScope
|
||||||
@Bean("emailSendStep")
|
@Bean("emailSendStep")
|
||||||
Step emailSendStep(Tasklet emailSendTasklet) {
|
Step emailSendStep(Tasklet emailSendTasklet) {
|
||||||
log.info(">>> emailSendStep");
|
log.info(">>> emailSendStep");
|
||||||
return new StepBuilder("emailSendStep", jobRepository)
|
return new StepBuilder("emailSendStep")
|
||||||
.tasklet(emailSendTasklet, transactionManager).build();
|
.tasklet(emailSendTasklet).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Tasklet emailSendTasklet(){
|
Tasklet emailSendTasklet() {
|
||||||
return ((contribution, chunkContext) -> {
|
return ((contribution, chunkContext) -> {
|
||||||
log.info(">>>>> emailSendTasklet");
|
log.info(">>>>> emailSendTasklet");
|
||||||
return RepeatStatus.FINISHED;
|
return RepeatStatus.FINISHED;
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ package com.spring.domain.email.entity;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.IdClass;
|
import javax.persistence.IdClass;
|
||||||
import jakarta.persistence.Lob;
|
import javax.persistence.Lob;
|
||||||
import jakarta.persistence.SequenceGenerator;
|
import javax.persistence.SequenceGenerator;
|
||||||
import jakarta.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package com.spring.domain.email.entity;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import jakarta.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package com.spring.domain.post.entity;
|
package com.spring.domain.post.entity;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.JoinColumns;
|
||||||
import jakarta.persistence.JoinColumns;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.JoinColumns;
|
||||||
import jakarta.persistence.JoinColumns;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ package com.spring.domain.quartz.entity;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.JoinColumns;
|
||||||
import jakarta.persistence.JoinColumns;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package com.spring.domain.quartz.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.ForeignKey;
|
||||||
import jakarta.persistence.ForeignKey;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.JoinColumns;
|
||||||
import jakarta.persistence.JoinColumns;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.spring.domain.user.api;
|
package com.spring.domain.user.api;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -11,7 +13,6 @@ import com.spring.domain.user.dto.SignInRequest;
|
|||||||
import com.spring.domain.user.service.AuthService;
|
import com.spring.domain.user.service.AuthService;
|
||||||
import com.spring.infra.security.jwt.JwtTokenService;
|
import com.spring.infra.security.jwt.JwtTokenService;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package com.spring.domain.user.entity;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import jakarta.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package com.spring.domain.user.entity;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import jakarta.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import jakarta.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package com.spring.domain.user.entity;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import javax.persistence.Embeddable;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import javax.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import jakarta.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import jakarta.persistence.MapsId;
|
import javax.persistence.MapsId;
|
||||||
import jakarta.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.spring.infra.batch;
|
||||||
|
|
||||||
|
public class BatchConfig {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,6 +11,18 @@ import org.springframework.context.annotation.Primary;
|
|||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터 소스 설정을 위한 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 애플리케이션의 주 데이터 소스를 설정하며, 다음과 같은 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>주 데이터 소스 속성 설정</li>
|
||||||
|
* <li>HikariCP를 사용한 주 데이터 소스 생성</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class PrimaryDataSourceConfig {
|
public class PrimaryDataSourceConfig {
|
||||||
|
|
||||||
@@ -19,6 +31,14 @@ public class PrimaryDataSourceConfig {
|
|||||||
private static final String DATASOURCE_PROPERTIES = "primaryDataSourceProperties";
|
private static final String DATASOURCE_PROPERTIES = "primaryDataSourceProperties";
|
||||||
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.primary";
|
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.primary";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터 소스의 속성을 설정합니다.
|
||||||
|
*
|
||||||
|
* <p>이 메소드는 'spring.datasource.primary' 접두사로 시작하는 설정을 읽어
|
||||||
|
* DataSourceProperties 객체를 생성합니다.</p>
|
||||||
|
*
|
||||||
|
* @return 설정된 DataSourceProperties 객체
|
||||||
|
*/
|
||||||
@Primary
|
@Primary
|
||||||
@Bean(name = DATASOURCE_PROPERTIES)
|
@Bean(name = DATASOURCE_PROPERTIES)
|
||||||
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
||||||
@@ -26,6 +46,14 @@ public class PrimaryDataSourceConfig {
|
|||||||
return new DataSourceProperties();
|
return new DataSourceProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터 소스를 생성합니다.
|
||||||
|
*
|
||||||
|
* <p>이 메소드는 설정된 DataSourceProperties를 사용하여 HikariCP 데이터 소스를 생성합니다.</p>
|
||||||
|
*
|
||||||
|
* @param properties 데이터 소스 속성
|
||||||
|
* @return 생성된 DataSource 객체
|
||||||
|
*/
|
||||||
@Primary
|
@Primary
|
||||||
@Bean(name = DATASOURCE)
|
@Bean(name = DATASOURCE)
|
||||||
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터 소스 설정을 위한 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 애플리케이션의 보조 데이터 소스를 설정하며, 다음과 같은 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>보조 데이터 소스 속성 설정</li>
|
||||||
|
* <li>HikariCP를 사용한 보조 데이터 소스 생성</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecondaryDataSourceConfig {
|
public class SecondaryDataSourceConfig {
|
||||||
|
|
||||||
@@ -18,12 +30,28 @@ public class SecondaryDataSourceConfig {
|
|||||||
private static final String DATASOURCE_PROPERTIES = "secondaryDataSourceProperties";
|
private static final String DATASOURCE_PROPERTIES = "secondaryDataSourceProperties";
|
||||||
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.secondary";
|
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.secondary";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터 소스의 속성을 설정합니다.
|
||||||
|
*
|
||||||
|
* <p>이 메소드는 'spring.datasource.secondary' 접두사로 시작하는 설정을 읽어
|
||||||
|
* DataSourceProperties 객체를 생성합니다.</p>
|
||||||
|
*
|
||||||
|
* @return 설정된 DataSourceProperties 객체
|
||||||
|
*/
|
||||||
@Bean(name = DATASOURCE_PROPERTIES)
|
@Bean(name = DATASOURCE_PROPERTIES)
|
||||||
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
||||||
DataSourceProperties dataSourceProperties() {
|
DataSourceProperties dataSourceProperties() {
|
||||||
return new DataSourceProperties();
|
return new DataSourceProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터 소스를 생성합니다.
|
||||||
|
*
|
||||||
|
* <p>이 메소드는 설정된 DataSourceProperties를 사용하여 HikariCP 데이터 소스를 생성합니다.</p>
|
||||||
|
*
|
||||||
|
* @param properties 데이터 소스 속성
|
||||||
|
* @return 생성된 DataSource 객체
|
||||||
|
*/
|
||||||
@Bean(name = DATASOURCE)
|
@Bean(name = DATASOURCE)
|
||||||
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
||||||
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.spring.infra.db.orm.jpa;
|
package com.spring.infra.db.orm.jpa;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -25,9 +26,21 @@ import com.spring.infra.db.PrimaryDataSourceConfig;
|
|||||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||||
import com.spring.infra.db.orm.jpa.util.EntityScanner;
|
import com.spring.infra.db.orm.jpa.util.EntityScanner;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManagerFactory;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스에 대한 JPA 설정을 담당하는 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 다음과 같은 주요 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>주 데이터베이스용 EntityManagerFactory 설정</li>
|
||||||
|
* <li>주 데이터베이스용 TransactionManager 설정</li>
|
||||||
|
* <li>JPA 리포지토리 활성화 및 필터링</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableJpaRepositories(
|
@EnableJpaRepositories(
|
||||||
basePackages = PrimaryJpaConfig.BASE_PACKAGE,
|
basePackages = PrimaryJpaConfig.BASE_PACKAGE,
|
||||||
@@ -46,6 +59,13 @@ public class PrimaryJpaConfig {
|
|||||||
private final JpaProperties jpaProperties;
|
private final JpaProperties jpaProperties;
|
||||||
private final HibernateProperties hibernateProperties;
|
private final HibernateProperties hibernateProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스용 EntityManagerFactory를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param builder EntityManagerFactory 빌더
|
||||||
|
* @param dataSource 주 데이터 소스
|
||||||
|
* @return 구성된 LocalContainerEntityManagerFactoryBean
|
||||||
|
*/
|
||||||
@Primary
|
@Primary
|
||||||
@Bean(name = ENTITY_MANAGER_FACTORY)
|
@Bean(name = ENTITY_MANAGER_FACTORY)
|
||||||
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
||||||
@@ -62,12 +82,23 @@ public class PrimaryJpaConfig {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스용 TransactionManager를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param factory 주 EntityManagerFactory
|
||||||
|
* @return 구성된 PlatformTransactionManager
|
||||||
|
*/
|
||||||
@Primary
|
@Primary
|
||||||
@Bean(TRANSACTION_MANAGER)
|
@Bean(TRANSACTION_MANAGER)
|
||||||
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
|
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
|
||||||
return new JpaTransactionManager(factory);
|
return new JpaTransactionManager(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 데이터베이스 선택을 위한 커스텀 필터 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 필터는 DatabaseSelector 어노테이션이 없는 엔티티만 선택합니다.</p>
|
||||||
|
*/
|
||||||
public static class DatabaseFilter implements TypeFilter {
|
public static class DatabaseFilter implements TypeFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
|
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.spring.infra.db.orm.jpa;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -26,9 +27,21 @@ import com.spring.infra.db.SecondaryDataSourceConfig;
|
|||||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||||
import com.spring.infra.db.orm.jpa.util.EntityScanner;
|
import com.spring.infra.db.orm.jpa.util.EntityScanner;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManagerFactory;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스에 대한 JPA 설정을 담당하는 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 다음과 같은 주요 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>보조 데이터베이스용 EntityManagerFactory 설정</li>
|
||||||
|
* <li>보조 데이터베이스용 TransactionManager 설정</li>
|
||||||
|
* <li>JPA 리포지토리 활성화 및 필터링</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableJpaRepositories(
|
@EnableJpaRepositories(
|
||||||
basePackages = SecondaryJpaConfig.BASE_PACKAGE,
|
basePackages = SecondaryJpaConfig.BASE_PACKAGE,
|
||||||
@@ -47,6 +60,13 @@ public class SecondaryJpaConfig {
|
|||||||
private final JpaProperties jpaProperties;
|
private final JpaProperties jpaProperties;
|
||||||
private final HibernateProperties hibernateProperties;
|
private final HibernateProperties hibernateProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스용 EntityManagerFactory를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param builder EntityManagerFactory 빌더
|
||||||
|
* @param dataSource 보조 데이터 소스
|
||||||
|
* @return 구성된 LocalContainerEntityManagerFactoryBean
|
||||||
|
*/
|
||||||
@Bean(name = ENTITY_MANAGER_FACTORY)
|
@Bean(name = ENTITY_MANAGER_FACTORY)
|
||||||
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
||||||
EntityManagerFactoryBuilder builder,
|
EntityManagerFactoryBuilder builder,
|
||||||
@@ -62,11 +82,22 @@ public class SecondaryJpaConfig {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스용 TransactionManager를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param factory 보조 EntityManagerFactory
|
||||||
|
* @return 구성된 PlatformTransactionManager
|
||||||
|
*/
|
||||||
@Bean(TRANSACTION_MANAGER)
|
@Bean(TRANSACTION_MANAGER)
|
||||||
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
|
PlatformTransactionManager transactionManager(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory factory) {
|
||||||
return new JpaTransactionManager(factory);
|
return new JpaTransactionManager(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 데이터베이스 선택을 위한 커스텀 필터 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 필터는 DatabaseSelector 어노테이션이 있고, 그 값이 보조 데이터베이스와 일치하는 엔티티만 선택합니다.</p>
|
||||||
|
*/
|
||||||
public static class DatabaseFilter implements TypeFilter {
|
public static class DatabaseFilter implements TypeFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
|
public boolean match(@NonNull MetadataReader reader, @NonNull MetadataReaderFactory factory) {
|
||||||
|
|||||||
@@ -5,8 +5,34 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 엔티티 클래스가 사용할 데이터베이스를 지정하는 어노테이션입니다.
|
||||||
|
*
|
||||||
|
* <p>이 어노테이션은 JPA 엔티티 클래스에 적용되며, 해당 엔티티가 어떤 데이터베이스에서
|
||||||
|
* 사용될지를 지정합니다. 주로 다중 데이터베이스 환경에서 엔티티를 특정 데이터베이스에
|
||||||
|
* 매핑하는 데 사용됩니다.</p>
|
||||||
|
*
|
||||||
|
* <p>사용 예:</p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* @Entity
|
||||||
|
* @DatabaseSelector("secondary")
|
||||||
|
* public class MyEntity {
|
||||||
|
* // 엔티티 필드 및 메소드
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface DatabaseSelector {
|
public @interface DatabaseSelector {
|
||||||
|
/**
|
||||||
|
* 엔티티가 사용할 데이터베이스의 이름을 지정합니다.
|
||||||
|
*
|
||||||
|
* @return 데이터베이스 이름
|
||||||
|
*/
|
||||||
String value();
|
String value();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.spring.infra.db.orm.jpa.util;
|
package com.spring.infra.db.orm.jpa.util;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
@@ -8,17 +10,38 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPA 엔티티 클래스를 스캔하는 유틸리티 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 지정된 패키지 내의 엔티티 클래스를 스캔하고,
|
||||||
|
* 선택적으로 특정 데이터베이스에 해당하는 엔티티만 필터링할 수 있습니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class EntityScanner {
|
public class EntityScanner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정된 기본 패키지 내의 모든 엔티티를 스캔합니다.
|
||||||
|
*
|
||||||
|
* @param basePackage 스캔할 기본 패키지
|
||||||
|
* @return 스캔된 엔티티 클래스의 패키지 이름 배열
|
||||||
|
*/
|
||||||
public static String[] scanEntities(String basePackage) {
|
public static String[] scanEntities(String basePackage) {
|
||||||
return scanEntities(basePackage, null);
|
return scanEntities(basePackage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정된 기본 패키지 내의 엔티티를 스캔하고, 선택적으로 특정 데이터베이스에 해당하는 엔티티만 필터링합니다.
|
||||||
|
*
|
||||||
|
* @param basePackage 스캔할 기본 패키지
|
||||||
|
* @param dbName 필터링할 데이터베이스 이름 (null이면 모든 엔티티 반환)
|
||||||
|
* @return 스캔된 엔티티 클래스의 패키지 이름 배열
|
||||||
|
*/
|
||||||
public static String[] scanEntities(String basePackage, String dbName) {
|
public static String[] scanEntities(String basePackage, String dbName) {
|
||||||
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||||
scanner.addIncludeFilter((MetadataReader reader, MetadataReaderFactory factory) -> {
|
scanner.addIncludeFilter((MetadataReader reader, MetadataReaderFactory factory) -> {
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|||||||
import com.spring.infra.db.PrimaryDataSourceConfig;
|
import com.spring.infra.db.PrimaryDataSourceConfig;
|
||||||
import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
|
import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스에 대한 MyBatis 설정을 담당하는 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 다음과 같은 주요 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>주 데이터베이스용 SqlSessionFactory 설정</li>
|
||||||
|
* <li>MyBatis 매퍼 스캔 설정</li>
|
||||||
|
* <li>MyBatis 관련 설정 (카멜 케이스 변환, null 처리 등)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(
|
@MapperScan(
|
||||||
basePackages = {PrimaryMybatisConfig.BASE_PACKAGE},
|
basePackages = {PrimaryMybatisConfig.BASE_PACKAGE},
|
||||||
@@ -29,6 +42,13 @@ public class PrimaryMybatisConfig {
|
|||||||
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
||||||
private static final String SESSION_FACTORY = "primarySqlSessionFactory";
|
private static final String SESSION_FACTORY = "primarySqlSessionFactory";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스용 SqlSessionFactory를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param dataSource 주 데이터 소스
|
||||||
|
* @return 구성된 SqlSessionFactory 객체
|
||||||
|
* @throws Exception SqlSessionFactory 생성 중 발생할 수 있는 예외
|
||||||
|
*/
|
||||||
@Primary
|
@Primary
|
||||||
@Bean(name = SESSION_FACTORY)
|
@Bean(name = SESSION_FACTORY)
|
||||||
SqlSessionFactory sqlSessionFactory(@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
SqlSessionFactory sqlSessionFactory(@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
||||||
@@ -41,6 +61,11 @@ public class PrimaryMybatisConfig {
|
|||||||
return sessionFactory.getObject();
|
return sessionFactory.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBatis 설정을 구성합니다.
|
||||||
|
*
|
||||||
|
* @return 구성된 MyBatis Configuration 객체
|
||||||
|
*/
|
||||||
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
||||||
var configuration = new org.apache.ibatis.session.Configuration();
|
var configuration = new org.apache.ibatis.session.Configuration();
|
||||||
configuration.setMapUnderscoreToCamelCase(true);
|
configuration.setMapUnderscoreToCamelCase(true);
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|||||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||||
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
|
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스에 대한 MyBatis 설정을 담당하는 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 다음과 같은 주요 기능을 제공합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>보조 데이터베이스용 SqlSessionFactory 설정</li>
|
||||||
|
* <li>MyBatis 매퍼 스캔 설정</li>
|
||||||
|
* <li>MyBatis 관련 설정 (카멜 케이스 변환, null 처리 등)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(
|
@MapperScan(
|
||||||
basePackages = {SecondaryMybatisConfig.BASE_PACKAGE},
|
basePackages = {SecondaryMybatisConfig.BASE_PACKAGE},
|
||||||
@@ -28,6 +41,13 @@ public class SecondaryMybatisConfig {
|
|||||||
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
||||||
private static final String SESSION_FACTORY = "secondarySqlSessionFactory";
|
private static final String SESSION_FACTORY = "secondarySqlSessionFactory";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스용 SqlSessionFactory를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param dataSource 보조 데이터 소스
|
||||||
|
* @return 구성된 SqlSessionFactory 객체
|
||||||
|
* @throws Exception SqlSessionFactory 생성 중 발생할 수 있는 예외
|
||||||
|
*/
|
||||||
@Bean(name = SESSION_FACTORY)
|
@Bean(name = SESSION_FACTORY)
|
||||||
SqlSessionFactory sqlSessionFactory(@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
SqlSessionFactory sqlSessionFactory(@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
||||||
var sessionFactory = new SqlSessionFactoryBean();
|
var sessionFactory = new SqlSessionFactoryBean();
|
||||||
@@ -39,6 +59,11 @@ public class SecondaryMybatisConfig {
|
|||||||
return sessionFactory.getObject();
|
return sessionFactory.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBatis 설정을 구성합니다.
|
||||||
|
*
|
||||||
|
* @return 구성된 MyBatis Configuration 객체
|
||||||
|
*/
|
||||||
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
private org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
||||||
var configuration = new org.apache.ibatis.session.Configuration();
|
var configuration = new org.apache.ibatis.session.Configuration();
|
||||||
configuration.setMapUnderscoreToCamelCase(true);
|
configuration.setMapUnderscoreToCamelCase(true);
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주 데이터베이스용 MyBatis 매퍼를 지정하는 어노테이션입니다.
|
||||||
|
*
|
||||||
|
* <p>이 어노테이션은 인터페이스 레벨에서 사용되며, 해당 인터페이스가 주 데이터베이스와
|
||||||
|
* 연결된 MyBatis 매퍼임을 나타냅니다. 주로 다중 데이터베이스 환경에서 매퍼 인터페이스를
|
||||||
|
* 특정 데이터베이스에 연결하는 데 사용됩니다.</p>
|
||||||
|
*
|
||||||
|
* <p>사용 예:</p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* @PrimaryMapper
|
||||||
|
* public interface UserMapper {
|
||||||
|
* // 매퍼 메소드 정의
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface PrimaryMapper {
|
public @interface PrimaryMapper {
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 보조 데이터베이스용 MyBatis 매퍼를 지정하는 어노테이션입니다.
|
||||||
|
*
|
||||||
|
* <p>이 어노테이션은 인터페이스 레벨에서 사용되며, 해당 인터페이스가 보조 데이터베이스와
|
||||||
|
* 연결된 MyBatis 매퍼임을 나타냅니다. 주로 다중 데이터베이스 환경에서 매퍼 인터페이스를
|
||||||
|
* 특정 데이터베이스에 연결하는 데 사용됩니다.</p>
|
||||||
|
*
|
||||||
|
* <p>사용 예:</p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* @SecondaryMapper
|
||||||
|
* public interface UserMapper {
|
||||||
|
* // 매퍼 메소드 정의
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface SecondaryMapper {
|
public @interface SecondaryMapper {
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 스케줄러 설정을 위한 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 Quartz 스케줄러의 기본 설정을 제공하며, 다음과 같은 기능을 수행합니다:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>JobFactory 빈 생성 및 설정</li>
|
||||||
|
* <li>SchedulerFactoryBean 생성 및 설정</li>
|
||||||
|
* <li>Quartz 작업에 대한 의존성 주입 지원</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class QuartzConfig {
|
public class QuartzConfig {
|
||||||
@@ -23,10 +36,13 @@ public class QuartzConfig {
|
|||||||
private final PlatformTransactionManager transactionManager;
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quartz Schedule Job 에 의존성 주입
|
* Quartz Schedule Job에 의존성을 주입하기 위한 JobFactory를 생성합니다.
|
||||||
*
|
*
|
||||||
* @param beanFactory application context beanFactory
|
* <p>이 메소드는 Spring의 AutowireCapableBeanFactory를 사용하여
|
||||||
* @return the job factory
|
* Quartz Job 인스턴스에 자동으로 의존성을 주입합니다.</p>
|
||||||
|
*
|
||||||
|
* @param beanFactory Spring의 AutowireCapableBeanFactory
|
||||||
|
* @return 생성된 JobFactory 인스턴스
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
JobFactory jobFactory(AutowireCapableBeanFactory beanFactory) {
|
JobFactory jobFactory(AutowireCapableBeanFactory beanFactory) {
|
||||||
@@ -38,10 +54,19 @@ public class QuartzConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scheduler 전체를 관리하는 Manager.
|
* Quartz Scheduler를 생성하고 설정하는 SchedulerFactoryBean을 구성합니다.
|
||||||
*
|
*
|
||||||
* @param jobFactory job factory
|
* <p>이 메소드는 다음과 같은 설정을 수행합니다:</p>
|
||||||
* @return the scheduler factory bean
|
* <ul>
|
||||||
|
* <li>스케줄러 이름 설정</li>
|
||||||
|
* <li>Quartz 속성 설정</li>
|
||||||
|
* <li>데이터 소스 및 트랜잭션 매니저 설정</li>
|
||||||
|
* <li>JobFactory 설정</li>
|
||||||
|
* <li>자동 시작 및 종료 시 작업 완료 대기 설정</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param jobFactory 사용할 JobFactory 인스턴스
|
||||||
|
* @return 구성된 SchedulerFactoryBean 인스턴스
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) {
|
SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) {
|
||||||
|
|||||||
@@ -5,9 +5,50 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 스케줄러를 통해 실행될 작업을 정의하는 어노테이션입니다.
|
||||||
|
*
|
||||||
|
* <p>이 어노테이션은 메소드 레벨에서 사용되며, 해당 메소드를 Quartz 작업으로 등록합니다.</p>
|
||||||
|
*
|
||||||
|
* <p>주요 기능:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>작업의 이름 지정</li>
|
||||||
|
* <li>작업의 실행 주기를 Cron 표현식으로 정의</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>사용 예:</p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* @QuartzJob(name = "myJob", cronExpression = "0 0 12 * * ?")
|
||||||
|
* public void myScheduledJob() {
|
||||||
|
* // 작업 내용
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
public @interface QuartzJob {
|
public @interface QuartzJob {
|
||||||
|
/**
|
||||||
|
* Quartz 작업의 그룹을 지정합니다.
|
||||||
|
*
|
||||||
|
* @return 그룹의 이름
|
||||||
|
*/
|
||||||
|
String group() default "DEFAULT";
|
||||||
|
/**
|
||||||
|
* Quartz 작업의 이름을 지정합니다.
|
||||||
|
*
|
||||||
|
* @return 작업의 이름
|
||||||
|
*/
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 작업의 실행 주기를 Cron 표현식으로 지정합니다.
|
||||||
|
*
|
||||||
|
* @return Cron 표현식
|
||||||
|
*/
|
||||||
String cronExpression();
|
String cronExpression();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,24 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 작업을 실행하는 Spring Batch Job 실행기 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 Quartz 스케줄러에 의해 호출되며, 지정된 Spring Batch Job을 실행합니다.</p>
|
||||||
|
*
|
||||||
|
* <p>주요 기능:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>JobRegistry에서 지정된 이름의 Job을 조회</li>
|
||||||
|
* <li>Job 실행을 위한 JobParameters 생성</li>
|
||||||
|
* <li>JobLauncher를 통한 Job 실행</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
* @see QuartzJobBean
|
||||||
|
* @see JobLauncher
|
||||||
|
* @see JobRegistry
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class QuartzJobLauncher extends QuartzJobBean {
|
public class QuartzJobLauncher extends QuartzJobBean {
|
||||||
@@ -26,6 +44,18 @@ public class QuartzJobLauncher extends QuartzJobBean {
|
|||||||
this.jobName = jobName;
|
this.jobName = jobName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 스케줄러에 의해 호출되는 메소드로, 실제 Job을 실행합니다.
|
||||||
|
*
|
||||||
|
* <p>이 메소드는 다음과 같은 작업을 수행합니다:</p>
|
||||||
|
* <ol>
|
||||||
|
* <li>JobRegistry에서 지정된 이름의 Job을 조회</li>
|
||||||
|
* <li>현재 시간을 기반으로 한 고유한 JobParameters 생성</li>
|
||||||
|
* <li>JobLauncher를 사용하여 Job 실행</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param context Quartz JobExecutionContext 객체
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void executeInternal(@NonNull JobExecutionContext context) throws JobExecutionException {
|
protected void executeInternal(@NonNull JobExecutionContext context) throws JobExecutionException {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,6 +19,29 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 작업 등록기 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 애플리케이션 컨텍스트가 리프레시될 때 실행되며,
|
||||||
|
* {@link QuartzJob} 어노테이션이 붙은 모든 메소드를 찾아 Quartz 스케줄러에 등록합니다.</p>
|
||||||
|
*
|
||||||
|
* <p>주요 기능:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>애플리케이션 컨텍스트 내의 모든 빈을 검사</li>
|
||||||
|
* <li>QuartzJob 어노테이션이 붙은 메소드 식별</li>
|
||||||
|
* <li>식별된 메소드를 Quartz 작업으로 등록</li>
|
||||||
|
* <li>각 작업에 대한 JobDetail 및 Trigger 생성</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 {@link ApplicationListener}를 구현하여
|
||||||
|
* {@link ContextRefreshedEvent}가 발생할 때 자동으로 실행됩니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
* @see QuartzJob
|
||||||
|
* @see ApplicationListener
|
||||||
|
* @see ContextRefreshedEvent
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedEvent> {
|
public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
@@ -26,6 +49,12 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
|
|||||||
private final Scheduler scheduler;
|
private final Scheduler scheduler;
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 애플리케이션 컨텍스트가 리프레시될 때 호출되는 메소드.
|
||||||
|
* 모든 빈을 순회하며 QuartzJob 어노테이션이 붙은 메소드를 찾아 Quartz 작업으로 등록합니다.
|
||||||
|
*
|
||||||
|
* @param event ContextRefreshedEvent 객체
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
||||||
for (String beanName : applicationContext.getBeanDefinitionNames()) {
|
for (String beanName : applicationContext.getBeanDefinitionNames()) {
|
||||||
@@ -40,31 +69,50 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QuartzJob 어노테이션 정보를 바탕으로 Quartz 작업을 등록합니다.
|
||||||
|
*
|
||||||
|
* @param quartzJobAnnotation QuartzJob 어노테이션 객체
|
||||||
|
* @param jobName 등록할 작업의 이름
|
||||||
|
*/
|
||||||
private void registerQuartzJob(QuartzJob quartzJobAnnotation, String jobName) {
|
private void registerQuartzJob(QuartzJob quartzJobAnnotation, String jobName) {
|
||||||
try {
|
try {
|
||||||
JobDetail jobDetail = createJobDetail(quartzJobAnnotation, jobName);
|
JobDetail jobDetail = createJobDetail(quartzJobAnnotation, jobName);
|
||||||
Trigger trigger = createTrigger(quartzJobAnnotation, jobDetail);
|
Trigger trigger = createTrigger(quartzJobAnnotation, jobDetail);
|
||||||
scheduler.scheduleJob(jobDetail, trigger);
|
scheduler.scheduleJob(jobDetail, trigger);
|
||||||
} catch (SchedulerException e) {
|
} catch (SchedulerException e) {
|
||||||
e.printStackTrace();
|
|
||||||
throw new IllegalStateException("Error scheduling Quartz job", e);
|
throw new IllegalStateException("Error scheduling Quartz job", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QuartzJob 어노테이션 정보를 바탕으로 JobDetail 객체를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param quartzJobAnnotation QuartzJob 어노테이션 객체
|
||||||
|
* @param jobName 작업의 이름
|
||||||
|
* @return 생성된 JobDetail 객체
|
||||||
|
*/
|
||||||
private JobDetail createJobDetail(QuartzJob quartzJobAnnotation, String jobName) {
|
private JobDetail createJobDetail(QuartzJob quartzJobAnnotation, String jobName) {
|
||||||
JobDataMap jobDataMap = new JobDataMap();
|
JobDataMap jobDataMap = new JobDataMap();
|
||||||
jobDataMap.put("jobName", jobName);
|
jobDataMap.put("jobName", jobName);
|
||||||
return JobBuilder.newJob(QuartzJobLauncher.class)
|
return JobBuilder.newJob(QuartzJobLauncher.class)
|
||||||
.withIdentity(quartzJobAnnotation.name())
|
.withIdentity(quartzJobAnnotation.name(), quartzJobAnnotation.group())
|
||||||
.setJobData(jobDataMap)
|
.setJobData(jobDataMap)
|
||||||
.storeDurably()
|
.storeDurably()
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QuartzJob 어노테이션 정보를 바탕으로 Trigger 객체를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param quartzJobAnnotation QuartzJob 어노테이션 객체
|
||||||
|
* @param jobDetail 연관된 JobDetail 객체
|
||||||
|
* @return 생성된 Trigger 객체
|
||||||
|
*/
|
||||||
private Trigger createTrigger(QuartzJob quartzJobAnnotation, JobDetail jobDetail) {
|
private Trigger createTrigger(QuartzJob quartzJobAnnotation, JobDetail jobDetail) {
|
||||||
return TriggerBuilder.newTrigger()
|
return TriggerBuilder.newTrigger()
|
||||||
.forJob(jobDetail)
|
.forJob(jobDetail)
|
||||||
.withIdentity(quartzJobAnnotation.name() + "Trigger")
|
.withIdentity(quartzJobAnnotation.name() + "Trigger", quartzJobAnnotation.group())
|
||||||
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJobAnnotation.cronExpression()))
|
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJobAnnotation.cronExpression()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,21 @@ import lombok.Getter;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 스케줄러의 설정 속성을 관리하는 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 application.properties 또는 application.yml 파일에서
|
||||||
|
* "spring.quartz.properties.org.quartz" 접두사로 시작하는 설정을 읽어옵니다.</p>
|
||||||
|
*
|
||||||
|
* <p>주요 기능:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>JobStore, Scheduler, ThreadPool 관련 설정 관리</li>
|
||||||
|
* <li>설정값을 Properties 객체로 변환</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -45,18 +60,38 @@ public class QuartzProperties {
|
|||||||
private final int threadPriority;
|
private final int threadPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 객체의 모든 속성을 Properties 객체로 변환합니다.
|
||||||
|
*
|
||||||
|
* @return 변환된 Properties 객체
|
||||||
|
*/
|
||||||
public Properties toProperties() {
|
public Properties toProperties() {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
addProperties(PREFIX, this, properties);
|
addProperties(PREFIX, this, properties);
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주어진 객체의 모든 필드를 재귀적으로 순회하며 Properties 객체에 추가합니다.
|
||||||
|
*
|
||||||
|
* @param prefix 속성 키의 접두사
|
||||||
|
* @param object 속성을 추출할 객체
|
||||||
|
* @param properties 속성을 저장할 Properties 객체
|
||||||
|
*/
|
||||||
private void addProperties(String prefix, Object object, Properties properties) {
|
private void addProperties(String prefix, Object object, Properties properties) {
|
||||||
Arrays.stream(object.getClass().getDeclaredFields())
|
Arrays.stream(object.getClass().getDeclaredFields())
|
||||||
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
||||||
.forEach(field -> setProperties(prefix, object, properties, field));
|
.forEach(field -> setProperties(prefix, object, properties, field));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주어진 필드의 값을 Properties 객체에 추가합니다.
|
||||||
|
*
|
||||||
|
* @param prefix 속성 키의 접두사
|
||||||
|
* @param object 속성을 추출할 객체
|
||||||
|
* @param properties 속성을 저장할 Properties 객체
|
||||||
|
* @param field 처리할 필드
|
||||||
|
*/
|
||||||
private void setProperties(String prefix, Object object, Properties properties, Field field) {
|
private void setProperties(String prefix, Object object, Properties properties, Field field) {
|
||||||
try {
|
try {
|
||||||
Object value = field.get(object);
|
Object value = field.get(object);
|
||||||
@@ -74,6 +109,12 @@ public class QuartzProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주어진 타입이 단순 타입(primitive, String, Number, Boolean, Character)인지 확인합니다.
|
||||||
|
*
|
||||||
|
* @param type 확인할 클래스 타입
|
||||||
|
* @return 단순 타입이면 true, 그렇지 않으면 false
|
||||||
|
*/
|
||||||
private boolean isSimpleType(Class<?> type) {
|
private boolean isSimpleType(Class<?> type) {
|
||||||
return type.isPrimitive()
|
return type.isPrimitive()
|
||||||
|| String.class == type
|
|| String.class == type
|
||||||
|
|||||||
@@ -23,16 +23,30 @@ import com.spring.infra.security.handler.JwtAccessDeniedHandler;
|
|||||||
import com.spring.infra.security.handler.JwtAuthenticationEntryPoint;
|
import com.spring.infra.security.handler.JwtAuthenticationEntryPoint;
|
||||||
import com.spring.infra.security.jwt.JwtTokenService;
|
import com.spring.infra.security.jwt.JwtTokenService;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
/**
|
||||||
|
* 애플리케이션의 보안 설정을 담당하는 구성 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 Spring Security를 구성하고 JWT 기반의 인증을 설정합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private static final String[] PERMITTED_URI = {"/favicon.ico", "/api/auth/**", "/signIn", "/h2-console/**"};
|
private static final String[] PERMITTED_URI = {"/favicon.ico", "/api/auth/**", "/signIn", "/h2-console/**"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security의 필터 체인을 구성합니다.
|
||||||
|
*
|
||||||
|
* @param http HttpSecurity 객체
|
||||||
|
* @param tokenService JWT 토큰 서비스
|
||||||
|
* @param authenticationEntryPoint JWT 인증 진입점
|
||||||
|
* @param accessDeniedHandler JWT 접근 거부 핸들러
|
||||||
|
* @return 구성된 SecurityFilterChain
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain securityFilterChain(
|
SecurityFilterChain securityFilterChain(
|
||||||
HttpSecurity http,
|
HttpSecurity http,
|
||||||
@@ -46,7 +60,7 @@ public class SecurityConfig {
|
|||||||
.httpBasic(HttpBasicConfigurer::disable)
|
.httpBasic(HttpBasicConfigurer::disable)
|
||||||
.formLogin(FormLoginConfigurer::disable)
|
.formLogin(FormLoginConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers(PERMITTED_URI).permitAll()
|
.antMatchers(PERMITTED_URI).permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.logout(logout -> logout
|
.logout(logout -> logout
|
||||||
@@ -63,11 +77,21 @@ public class SecurityConfig {
|
|||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 요청에 대해 보안 검사를 무시하도록 설정합니다.
|
||||||
|
*
|
||||||
|
* @return WebSecurityCustomizer 객체
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
WebSecurityCustomizer ignoringCustomizer() {
|
WebSecurityCustomizer ignoringCustomizer() {
|
||||||
return web -> web.ignoring().requestMatchers("/h2-console/**");
|
return web -> web.ignoring().antMatchers("/h2-console/**");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비밀번호 인코더를 구성합니다.
|
||||||
|
*
|
||||||
|
* @return PasswordEncoder 객체
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
PasswordEncoder passwordEncoder() {
|
PasswordEncoder passwordEncoder() {
|
||||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||||
|
|||||||
@@ -12,16 +12,34 @@ import com.spring.domain.user.entity.AppUser;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security의 UserDetails 인터페이스를 구현한 사용자 주체(Principal) 클래스입니다.
|
||||||
|
* <p>애플리케이션의 사용자 정보를 Spring Security에서 사용할 수 있는 형태로 변환합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public final class UserPrincipal implements UserDetails {
|
public final class UserPrincipal implements UserDetails {
|
||||||
|
|
||||||
private final AppUser appUser;
|
private final transient AppUser appUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppUser 객체로부터 UserPrincipal 객체를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param appUser 변환할 AppUser 객체
|
||||||
|
* @return 생성된 UserPrincipal 객체
|
||||||
|
*/
|
||||||
public static UserPrincipal valueOf(AppUser appUser) {
|
public static UserPrincipal valueOf(AppUser appUser) {
|
||||||
return new UserPrincipal(appUser);
|
return new UserPrincipal(appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자의 권한 목록을 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 사용자의 GrantedAuthority 컬렉션
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return appUser.getAppUserRoleMap().stream()
|
return appUser.getAppUserRoleMap().stream()
|
||||||
@@ -30,31 +48,62 @@ public final class UserPrincipal implements UserDetails {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계정이 만료되지 않았는지 확인합니다.
|
||||||
|
*
|
||||||
|
* @return 계정 만료 여부 (true: 만료되지 않음)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isAccountNonExpired() {
|
public boolean isAccountNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계정이 잠기지 않았는지 확인합니다.
|
||||||
|
*
|
||||||
|
* @return 계정 잠금 여부 (true: 잠기지 않음)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isAccountNonLocked() {
|
public boolean isAccountNonLocked() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 자격 증명(비밀번호)이 만료되지 않았는지 확인합니다.
|
||||||
|
*
|
||||||
|
* @return 자격 증명 만료 여부 (true: 만료되지 않음)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isCredentialsNonExpired() {
|
public boolean isCredentialsNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 계정이 활성화되어 있는지 확인합니다.
|
||||||
|
*
|
||||||
|
* @return 계정 활성화 여부 (true: 활성화됨)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자의 비밀번호를 반환합니다.
|
||||||
|
* 이 구현에서는 null을 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 사용자 비밀번호 (null)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자의 로그인 ID를 반환합니다.
|
||||||
|
*
|
||||||
|
* @return 사용자 로그인 ID
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return appUser.getLoginId();
|
return appUser.getLoginId();
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ package com.spring.infra.security.filter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
@@ -11,21 +16,39 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||||||
import com.spring.infra.security.jwt.JwtTokenRule;
|
import com.spring.infra.security.jwt.JwtTokenRule;
|
||||||
import com.spring.infra.security.jwt.JwtTokenService;
|
import com.spring.infra.security.jwt.JwtTokenService;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 인증을 처리하는 필터 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 필터는 요청마다 JWT 토큰을 검증하고, 필요한 경우 토큰을 갱신합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final JwtTokenService jwtTokenService;
|
private final JwtTokenService jwtTokenService;
|
||||||
private final List<String> permitAllUrls;
|
private final List<String> permitAllUrls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청마다 실행되는 필터 메소드입니다.
|
||||||
|
*
|
||||||
|
* <p>JWT 토큰을 검증하고, 필요한 경우 토큰을 갱신합니다.</p>
|
||||||
|
*
|
||||||
|
* @param request HTTP 요청 객체
|
||||||
|
* @param response HTTP 응답 객체
|
||||||
|
* @param filterChain 필터 체인
|
||||||
|
* @throws ServletException 서블릿 예외
|
||||||
|
* @throws IOException 입출력 예외
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
|
protected void doFilterInternal(
|
||||||
throws ServletException, IOException {
|
@NonNull HttpServletRequest request,
|
||||||
|
@NonNull HttpServletResponse response,
|
||||||
|
@NonNull FilterChain filterChain
|
||||||
|
) throws ServletException, IOException {
|
||||||
|
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
if (permitAllUrls.stream().anyMatch(requestURI::startsWith)) {
|
if (permitAllUrls.stream().anyMatch(requestURI::startsWith)) {
|
||||||
@@ -51,11 +74,15 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jwtTokenService.deleteCookie(response);
|
jwtTokenService.deleteCookie(response);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAuthenticationToContext(String accessToken) {
|
/**
|
||||||
Authentication authentication = jwtTokenService.getAuthentication(accessToken);
|
* 인증 정보를 SecurityContext에 설정합니다.
|
||||||
|
*
|
||||||
|
* @param token 토큰
|
||||||
|
*/
|
||||||
|
private void setAuthenticationToContext(String token) {
|
||||||
|
Authentication authentication = jwtTokenService.getAuthentication(token);
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,38 @@ package com.spring.infra.security.handler;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 유저 정보는 있으나 자원에 접근할 수 있는 권한이 없는 경우 : SC_FORBIDDEN (403) 응답
|
* JWT 인증에서 접근 거부 상황을 처리하는 핸들러 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 사용자가 인증은 되었지만 특정 리소스에 대한 접근 권한이 없는 경우를 처리합니다.
|
||||||
|
* 이런 경우 SC_FORBIDDEN (403) 응답을 반환합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 접근 거부 상황을 처리합니다.
|
||||||
|
*
|
||||||
|
* <p>사용자가 접근 권한이 없는 리소스에 접근을 시도할 때 호출됩니다.
|
||||||
|
* 이 메소드는 SC_FORBIDDEN (403) 상태 코드를 응답으로 전송합니다.</p>
|
||||||
|
*
|
||||||
|
* @param request 현재 HTTP 요청
|
||||||
|
* @param response 현재 HTTP 응답
|
||||||
|
* @param accessDeniedException 발생한 접근 거부 예외
|
||||||
|
* @throws IOException 입출력 예외 발생 시
|
||||||
|
* @throws ServletException 서블릿 예외 발생 시
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||||
|
|||||||
@@ -2,20 +2,38 @@ package com.spring.infra.security.handler;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 유저 정보 없이 접근한 경우 : SC_UNAUTHORIZED (401) 응답
|
* JWT 인증에서 인증되지 않은 접근을 처리하는 진입점 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 사용자가 인증되지 않은 상태에서 보호된 리소스에 접근을 시도할 때 호출됩니다.
|
||||||
|
* 이런 경우 SC_UNAUTHORIZED (401) 응답을 반환합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인증되지 않은 접근을 처리합니다.
|
||||||
|
*
|
||||||
|
* <p>인증되지 않은 사용자가 보호된 리소스에 접근을 시도할 때 호출됩니다.
|
||||||
|
* 이 메소드는 SC_UNAUTHORIZED (401) 상태 코드를 응답으로 전송합니다.</p>
|
||||||
|
*
|
||||||
|
* @param request 현재 HTTP 요청
|
||||||
|
* @param response 현재 HTTP 응답
|
||||||
|
* @param authException 발생한 인증 예외
|
||||||
|
* @throws IOException 입출력 예외 발생 시
|
||||||
|
* @throws ServletException 서블릿 예외 발생 시
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||||
AuthenticationException authException) throws IOException, ServletException {
|
AuthenticationException authException) throws IOException, ServletException {
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT(JSON Web Token) 관련 설정 속성을 관리하는 클래스입니다.
|
||||||
|
* 'jwt' 접두사로 시작하는 설정 속성을 바인딩합니다.
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@ConfigurationProperties(prefix = "jwt")
|
@ConfigurationProperties(prefix = "jwt")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -17,12 +17,26 @@ import io.jsonwebtoken.Jwts;
|
|||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰을 생성하는 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 액세스 토큰과 리프레시 토큰을 생성하는 기능을 제공합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtTokenGenerator {
|
public class JwtTokenGenerator {
|
||||||
|
|
||||||
private final JwtProperties jwtProperties;
|
private final JwtProperties jwtProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 액세스 토큰을 생성합니다.
|
||||||
|
*
|
||||||
|
* @param authentication 인증 정보
|
||||||
|
* @return 생성된 액세스 토큰
|
||||||
|
*/
|
||||||
public String generateAccessToken(Authentication authentication) {
|
public String generateAccessToken(Authentication authentication) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setHeader(createHeader())
|
.setHeader(createHeader())
|
||||||
@@ -33,6 +47,12 @@ public class JwtTokenGenerator {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰을 생성합니다.
|
||||||
|
*
|
||||||
|
* @param authentication 인증 정보
|
||||||
|
* @return 생성된 리프레시 토큰
|
||||||
|
*/
|
||||||
public String generateRefreshToken(Authentication authentication) {
|
public String generateRefreshToken(Authentication authentication) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setHeader(createHeader())
|
.setHeader(createHeader())
|
||||||
@@ -42,6 +62,11 @@ public class JwtTokenGenerator {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 헤더를 생성합니다.
|
||||||
|
*
|
||||||
|
* @return JWT 헤더 맵
|
||||||
|
*/
|
||||||
private Map<String, Object> createHeader() {
|
private Map<String, Object> createHeader() {
|
||||||
Map<String, Object> header = new HashMap<>();
|
Map<String, Object> header = new HashMap<>();
|
||||||
header.put("typ", "JWT");
|
header.put("typ", "JWT");
|
||||||
@@ -49,6 +74,12 @@ public class JwtTokenGenerator {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 클레임을 생성합니다.
|
||||||
|
*
|
||||||
|
* @param authentication 인증 정보
|
||||||
|
* @return JWT 클레임 맵
|
||||||
|
*/
|
||||||
private Map<String, Object> createClaims(Authentication authentication) {
|
private Map<String, Object> createClaims(Authentication authentication) {
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put(JwtTokenRule.AUTHORITIES_KEY.getValue(), authentication.getAuthorities().stream()
|
claims.put(JwtTokenRule.AUTHORITIES_KEY.getValue(), authentication.getAuthorities().stream()
|
||||||
|
|||||||
@@ -3,15 +3,46 @@ package com.spring.infra.security.jwt;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 관련 규칙을 정의하는 열거형입니다.
|
||||||
|
*
|
||||||
|
* <p>이 열거형은 JWT 토큰 처리에 필요한 다양한 상수 값들을 정의합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum JwtTokenRule {
|
public enum JwtTokenRule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 발급 시 사용되는 HTTP 헤더 이름입니다.
|
||||||
|
*/
|
||||||
JWT_ISSUE_HEADER("Set-Cookie"),
|
JWT_ISSUE_HEADER("Set-Cookie"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 해석 시 사용되는 HTTP 헤더 이름입니다.
|
||||||
|
*/
|
||||||
JWT_RESOLVE_HEADER("Cookie"),
|
JWT_RESOLVE_HEADER("Cookie"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 액세스 토큰의 접두사입니다.
|
||||||
|
*/
|
||||||
ACCESS_PREFIX("access"),
|
ACCESS_PREFIX("access"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰의 접두사입니다.
|
||||||
|
*/
|
||||||
REFRESH_PREFIX("refresh"),
|
REFRESH_PREFIX("refresh"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearer 인증 스키마의 접두사입니다.
|
||||||
|
*/
|
||||||
BEARER_PREFIX("Bearer "),
|
BEARER_PREFIX("Bearer "),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 클레임에서 권한 정보를 나타내는 키입니다.
|
||||||
|
*/
|
||||||
AUTHORITIES_KEY("auth");
|
AUTHORITIES_KEY("auth");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ package com.spring.infra.security.jwt;
|
|||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.http.ResponseCookie;
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -11,10 +15,15 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.spring.infra.security.service.UserPrincipalService;
|
import com.spring.infra.security.service.UserPrincipalService;
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import jakarta.servlet.http.Cookie;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 관련 서비스를 제공하는 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 JWT 토큰의 생성, 검증, 해석 등 다양한 토큰 관련 기능을 제공합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class JwtTokenService {
|
public class JwtTokenService {
|
||||||
|
|
||||||
@@ -41,6 +50,13 @@ public class JwtTokenService {
|
|||||||
this.refreshExpiration = jwtProperties.getRefreshToken().getExpiration();
|
this.refreshExpiration = jwtProperties.getRefreshToken().getExpiration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 액세스 토큰을 생성하고 응답 헤더에 설정합니다.
|
||||||
|
*
|
||||||
|
* @param response HTTP 응답 객체
|
||||||
|
* @param authentication 인증 정보
|
||||||
|
* @return 생성된 액세스 토큰
|
||||||
|
*/
|
||||||
public String generateAccessToken(HttpServletResponse response, Authentication authentication) {
|
public String generateAccessToken(HttpServletResponse response, Authentication authentication) {
|
||||||
String accessToken = jwtTokenGenerator.generateAccessToken(authentication);
|
String accessToken = jwtTokenGenerator.generateAccessToken(authentication);
|
||||||
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.ACCESS_PREFIX.getValue(), accessToken, accessExpiration / 1000);
|
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.ACCESS_PREFIX.getValue(), accessToken, accessExpiration / 1000);
|
||||||
@@ -48,6 +64,13 @@ public class JwtTokenService {
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰을 생성하고 응답 헤더에 설정합니다.
|
||||||
|
*
|
||||||
|
* @param response HTTP 응답 객체
|
||||||
|
* @param authentication 인증 정보
|
||||||
|
* @return 생성된 리프레시 토큰
|
||||||
|
*/
|
||||||
public String generateRefreshToken(HttpServletResponse response, Authentication authentication) {
|
public String generateRefreshToken(HttpServletResponse response, Authentication authentication) {
|
||||||
String refreshToken = jwtTokenGenerator.generateRefreshToken(authentication);
|
String refreshToken = jwtTokenGenerator.generateRefreshToken(authentication);
|
||||||
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.REFRESH_PREFIX.getValue(), refreshToken, refreshExpiration / 1000);
|
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.REFRESH_PREFIX.getValue(), refreshToken, refreshExpiration / 1000);
|
||||||
@@ -55,6 +78,14 @@ public class JwtTokenService {
|
|||||||
return refreshToken;
|
return refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰을 쿠키로 설정합니다.
|
||||||
|
*
|
||||||
|
* @param tokenPrefix 토큰 접두사
|
||||||
|
* @param token 토큰 값
|
||||||
|
* @param maxAgeSeconds 쿠키 유효 시간(초)
|
||||||
|
* @return 생성된 ResponseCookie 객체
|
||||||
|
*/
|
||||||
private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long maxAgeSeconds) {
|
private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long maxAgeSeconds) {
|
||||||
return ResponseCookie.from(tokenPrefix, token)
|
return ResponseCookie.from(tokenPrefix, token)
|
||||||
.path("/")
|
.path("/")
|
||||||
@@ -65,14 +96,34 @@ public class JwtTokenService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 액세스 토큰의 유효성을 검증합니다.
|
||||||
|
*
|
||||||
|
* @param token 검증할 토큰
|
||||||
|
* @return 토큰의 유효성 여부
|
||||||
|
*/
|
||||||
public boolean validateAccessToken(String token) {
|
public boolean validateAccessToken(String token) {
|
||||||
return jwtTokenUtil.getTokenStatus(token, accessSecretKey) == JwtTokenStatus.AUTHENTICATED;
|
return jwtTokenUtil.getTokenStatus(token, accessSecretKey) == JwtTokenStatus.AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰의 유효성을 검증합니다.
|
||||||
|
*
|
||||||
|
* @param token 검증할 토큰
|
||||||
|
* @return 토큰의 유효성 여부
|
||||||
|
*/
|
||||||
public boolean validateRefreshToken(String token) {
|
public boolean validateRefreshToken(String token) {
|
||||||
return jwtTokenUtil.getTokenStatus(token, refreshSecretKey) == JwtTokenStatus.AUTHENTICATED;
|
return jwtTokenUtil.getTokenStatus(token, refreshSecretKey) == JwtTokenStatus.AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 쿠키에서 토큰을 추출합니다.
|
||||||
|
*
|
||||||
|
* @param request HTTP 요청 객체
|
||||||
|
* @param tokenPrefix 토큰 접두사
|
||||||
|
* @return 추출된 토큰
|
||||||
|
* @throws IllegalStateException 토큰이 없을 경우 발생
|
||||||
|
*/
|
||||||
public String resolveTokenFromCookie(HttpServletRequest request, JwtTokenRule tokenPrefix) {
|
public String resolveTokenFromCookie(HttpServletRequest request, JwtTokenRule tokenPrefix) {
|
||||||
Cookie[] cookies = request.getCookies();
|
Cookie[] cookies = request.getCookies();
|
||||||
if (cookies == null) {
|
if (cookies == null) {
|
||||||
@@ -81,11 +132,24 @@ public class JwtTokenService {
|
|||||||
return jwtTokenUtil.resolveTokenFromCookie(cookies, tokenPrefix);
|
return jwtTokenUtil.resolveTokenFromCookie(cookies, tokenPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰으로부터 인증 정보를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param token JWT 토큰
|
||||||
|
* @return 생성된 Authentication 객체
|
||||||
|
*/
|
||||||
public Authentication getAuthentication(String token) {
|
public Authentication getAuthentication(String token) {
|
||||||
UserDetails principal = userPrincipalService.loadUserByUsername(getUserPk(token, accessSecretKey));
|
UserDetails principal = userPrincipalService.loadUserByUsername(getUserPk(token, accessSecretKey));
|
||||||
return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities());
|
return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰에서 사용자 식별자를 추출합니다.
|
||||||
|
*
|
||||||
|
* @param token JWT 토큰
|
||||||
|
* @param secretKey 비밀 키
|
||||||
|
* @return 추출된 사용자 식별자
|
||||||
|
*/
|
||||||
private String getUserPk(String token, Key secretKey) {
|
private String getUserPk(String token, Key secretKey) {
|
||||||
return Jwts.parserBuilder()
|
return Jwts.parserBuilder()
|
||||||
.setSigningKey(secretKey)
|
.setSigningKey(secretKey)
|
||||||
@@ -95,6 +159,11 @@ public class JwtTokenService {
|
|||||||
.getSubject();
|
.getSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 액세스 토큰과 리프레시 토큰 쿠키를 삭제합니다.
|
||||||
|
*
|
||||||
|
* @param response HTTP 응답 객체
|
||||||
|
*/
|
||||||
public void deleteCookie(HttpServletResponse response) {
|
public void deleteCookie(HttpServletResponse response) {
|
||||||
Cookie accessCookie = jwtTokenUtil.resetToken(JwtTokenRule.ACCESS_PREFIX);
|
Cookie accessCookie = jwtTokenUtil.resetToken(JwtTokenRule.ACCESS_PREFIX);
|
||||||
Cookie refreshCookie = jwtTokenUtil.resetToken(JwtTokenRule.REFRESH_PREFIX);
|
Cookie refreshCookie = jwtTokenUtil.resetToken(JwtTokenRule.REFRESH_PREFIX);
|
||||||
|
|||||||
@@ -3,10 +3,29 @@ package com.spring.infra.security.jwt;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰의 상태를 나타내는 열거형입니다.
|
||||||
|
*
|
||||||
|
* <p>이 열거형은 JWT 토큰의 유효성 검증 결과를 표현하는 데 사용됩니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum JwtTokenStatus {
|
public enum JwtTokenStatus {
|
||||||
|
/**
|
||||||
|
* 토큰이 유효하고 인증된 상태를 나타냅니다.
|
||||||
|
*/
|
||||||
AUTHENTICATED,
|
AUTHENTICATED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰이 만료된 상태를 나타냅니다.
|
||||||
|
*/
|
||||||
EXPIRED,
|
EXPIRED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰이 유효하지 않은 상태를 나타냅니다.
|
||||||
|
*/
|
||||||
INVALID
|
INVALID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,35 @@ import java.security.Key;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import jakarta.servlet.http.Cookie;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 관련 유틸리티 기능을 제공하는 클래스입니다.
|
||||||
|
*
|
||||||
|
* <p>이 클래스는 JWT 토큰의 상태 확인, 쿠키에서 토큰 추출, 서명 키 생성 등의 기능을 제공합니다.</p>
|
||||||
|
*
|
||||||
|
* @author mindol
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class JwtTokenUtil {
|
public class JwtTokenUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰의 상태를 확인합니다.
|
||||||
|
*
|
||||||
|
* @param token 검증할 JWT 토큰
|
||||||
|
* @param secretKey 토큰 검증에 사용할 비밀 키
|
||||||
|
* @return 토큰의 상태 (AUTHENTICATED, EXPIRED, INVALID)
|
||||||
|
*/
|
||||||
public JwtTokenStatus getTokenStatus(String token, Key secretKey) {
|
public JwtTokenStatus getTokenStatus(String token, Key secretKey) {
|
||||||
try {
|
try {
|
||||||
Jwts.parserBuilder()
|
Jwts.parserBuilder()
|
||||||
@@ -34,6 +50,13 @@ public class JwtTokenUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 쿠키에서 특정 접두사를 가진 토큰을 추출합니다.
|
||||||
|
*
|
||||||
|
* @param cookies 쿠키 배열
|
||||||
|
* @param tokenPrefix 토큰 접두사
|
||||||
|
* @return 추출된 토큰 값 (없으면 빈 문자열)
|
||||||
|
*/
|
||||||
public String resolveTokenFromCookie(Cookie[] cookies, JwtTokenRule tokenPrefix) {
|
public String resolveTokenFromCookie(Cookie[] cookies, JwtTokenRule tokenPrefix) {
|
||||||
return Arrays.stream(cookies)
|
return Arrays.stream(cookies)
|
||||||
.filter(cookie -> cookie.getName().equals(tokenPrefix.getValue()))
|
.filter(cookie -> cookie.getName().equals(tokenPrefix.getValue()))
|
||||||
@@ -42,15 +65,33 @@ public class JwtTokenUtil {
|
|||||||
.orElse("");
|
.orElse("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주어진 비밀 키로 서명 키를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param secretKey 비밀 키 문자열
|
||||||
|
* @return 생성된 서명 키
|
||||||
|
*/
|
||||||
public Key getSigningKey(String secretKey) {
|
public Key getSigningKey(String secretKey) {
|
||||||
String encodedKey = encodeToBase64(secretKey);
|
String encodedKey = encodeToBase64(secretKey);
|
||||||
return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8));
|
return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열을 Base64로 인코딩합니다.
|
||||||
|
*
|
||||||
|
* @param secretKey 인코딩할 문자열
|
||||||
|
* @return Base64로 인코딩된 문자열
|
||||||
|
*/
|
||||||
private String encodeToBase64(String secretKey) {
|
private String encodeToBase64(String secretKey) {
|
||||||
return Base64.getEncoder().encodeToString(secretKey.getBytes());
|
return Base64.getEncoder().encodeToString(secretKey.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 토큰을 리셋하는 쿠키를 생성합니다.
|
||||||
|
*
|
||||||
|
* @param tokenPrefix 토큰 접두사
|
||||||
|
* @return 생성된 쿠키 객체
|
||||||
|
*/
|
||||||
public Cookie resetToken(JwtTokenRule tokenPrefix) {
|
public Cookie resetToken(JwtTokenRule tokenPrefix) {
|
||||||
Cookie cookie = new Cookie(tokenPrefix.getValue(), null);
|
Cookie cookie = new Cookie(tokenPrefix.getValue(), null);
|
||||||
cookie.setMaxAge(0);
|
cookie.setMaxAge(0);
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ spring:
|
|||||||
show-sql: true
|
show-sql: true
|
||||||
|
|
||||||
batch:
|
batch:
|
||||||
|
job:
|
||||||
|
enabled: true
|
||||||
jdbc:
|
jdbc:
|
||||||
initialize-schema: always
|
initialize-schema: always
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user