This commit is contained in:
mindol1004
2024-08-28 18:53:15 +09:00
parent cc8ca2ae51
commit ecbf7f8698
18 changed files with 438 additions and 87 deletions

View File

@@ -13,19 +13,7 @@
<version>0.0.1-SNAPSHOT</version>
<name>batch-quartz</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>11</java.version>
</properties>
@@ -60,6 +48,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -67,11 +59,7 @@
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>

View File

@@ -1,44 +1,45 @@
package com.spring.domain.email.batch;
import org.springframework.batch.core.Job;
import java.util.List;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import com.spring.infra.quartz.QuartzJob;
import com.spring.infra.batch.AbstractBatchJob;
import com.spring.infra.batch.BatchJobInfo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class EmailSendBatch {
@Component
@BatchJobInfo(
group = "EMAIL",
jobName = "emailSendJob",
cronExpression = "*/5 * * * * ?"
)
public class EmailSendBatch extends AbstractBatchJob {
@QuartzJob(group = "EMAIL", name = "emailSendJob", cronExpression = "*/3 * * * * ?")
// @JobScope
@Bean(name = "emailSendJob")
Job emailSendJob(Step emailSendStep) {
log.info(">>> emailSendJob");
return new JobBuilder("emailSendJob")
.start(emailSendStep)
.build();
@Override
protected List<Step> createSteps() {
return List.of(
addStep("emailSendJobStep1111", createTasklet()),
addStep("emailSendJobStep2222", createSendTasklet())
);
}
// @StepScope
@Bean("emailSendStep")
Step emailSendStep(Tasklet emailSendTasklet) {
log.info(">>> emailSendStep");
return new StepBuilder("emailSendStep")
.tasklet(emailSendTasklet).build();
}
@Bean
Tasklet emailSendTasklet() {
@Override
protected Tasklet createTasklet() {
return ((contribution, chunkContext) -> {
log.info(">>>>> emailSendTasklet");
log.info(">>>>> emailSendTasklet1111111");
return RepeatStatus.FINISHED;
});
}
private Tasklet createSendTasklet() {
return ((contribution, chunkContext) -> {
log.info(">>>>> emailSendTasklet2222222");
return RepeatStatus.FINISHED;
});
}

View File

@@ -0,0 +1,52 @@
package com.spring.domain.post.batch;
import java.util.List;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import com.spring.domain.post.entity.Post;
import com.spring.domain.post.mapper.PostMapper;
import com.spring.infra.batch.AbstractBatchJob;
import com.spring.infra.batch.BatchJobInfo;
import com.spring.infra.db.orm.jpa.SecondaryJpaConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@BatchJobInfo(
group = "POST",
jobName = "postCreateJob",
cronExpression = "*/5 * * * * ?"
)
@RequiredArgsConstructor
public class PostCreateBatch extends AbstractBatchJob {
private final PostMapper postMapper;
@Autowired
@Override
public void setTransactionManager(
@Qualifier(SecondaryJpaConfig.TRANSACTION_MANAGER) PlatformTransactionManager transactionManager
) {
super.setTransactionManager(transactionManager);
}
@Override
protected Tasklet createTasklet() {
return ((contribution, chunkContext) -> {
postMapper.save(Post.builder().title("testTitle").content("testPost").build());
log.info(">>>>> PostCreateBatchTasklet333333333");
List<Post> list = postMapper.findAll();
list.forEach(item -> log.info(item.getContent()));
return RepeatStatus.FINISHED;
});
}
}

View File

@@ -10,12 +10,14 @@ import javax.persistence.Table;
import com.spring.infra.db.SecondaryDataSourceConfig;
import com.spring.infra.db.orm.jpa.annotation.DatabaseSelector;
import lombok.Builder;
import lombok.Getter;
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
@Entity
@Table(name = "APP_POST")
@Getter
@Builder
public class Post {
@Id

View File

@@ -1,8 +1,14 @@
package com.spring.domain.post.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.spring.domain.post.entity.Post;
import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
@SecondaryMapper
public interface PostMapper {
List<Post> findAll();
void save(@Param("post") Post post);
}

View File

@@ -0,0 +1,174 @@
package com.spring.infra.batch;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
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.tasklet.Tasklet;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.transaction.PlatformTransactionManager;
/**
* 배치 작업을 정의하는 추상 클래스입니다.
* <p>
* 이 클래스는 배치 작업의 기본 설정 및 실행을 관리합니다.
* </p>
*
* @author mindol
* @version 1.0
*/
@Configuration
public abstract class AbstractBatchJob implements ApplicationContextAware {
private final BatchJobInfo batchJobInfo;
private ApplicationContext applicationContext;
private JobRepository jobRepository;
private PlatformTransactionManager transactionManager;
/**
* 기본 생성자입니다.
* <p>
* BatchJobInfo 어노테이션을 찾고, 없으면 예외를 발생시킵니다.
* </p>
*/
protected AbstractBatchJob() {
this.batchJobInfo = AnnotationUtils.findAnnotation(getClass(), BatchJobInfo.class);
if (this.batchJobInfo == null) {
throw new IllegalStateException("BatchJobInfo annotation is missing");
}
}
/**
* ApplicationContext를 설정합니다.
*
* @param applicationContext 설정할 ApplicationContext
* @throws BeansException Beans 예외
*/
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
registerJobBean();
}
/**
* 배치 작업을 Spring의 Bean으로 등록합니다.
*/
private void registerJobBean() {
var beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
var registry = (BeanDefinitionRegistry) beanFactory;
var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Job.class, this::createJob);
registry.registerBeanDefinition(jobName(), beanDefinitionBuilder.getBeanDefinition());
}
/**
* JobRepository를 설정합니다.
*
* @param jobRepository 설정할 JobRepository
*/
@Autowired
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
/**
* PlatformTransactionManager를 설정합니다.
*
* @param transactionManager 설정할 PlatformTransactionManager
*/
@Autowired
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 배치 작업을 생성합니다.
*
* @return 생성된 Job 객체
* @throws IllegalStateException STEP이 정의되지 않은 경우 예외 발생
*/
private Job createJob() {
List<Step> steps = createSteps();
if (steps.isEmpty()) {
throw new IllegalStateException("No steps defined for job: " + jobName());
}
var jobBuilder = new JobBuilder(jobName()).repository(jobRepository).start(steps.get(0));
for (int i = 1; i < steps.size(); i++) {
jobBuilder = jobBuilder.next(steps.get(i));
}
return jobBuilder.build();
}
/**
* 배치 작업의 STEP을 생성합니다.
*
* @return 생성된 Step 리스트
*/
protected List<Step> createSteps() {
List<Step> steps = new ArrayList<>();
steps.add(addStep(jobName() + "Step", createTasklet()));
return steps;
}
/**
* STEP을 추가합니다.
*
* @param stepName STEP의 이름
* @param tasklet STEP에서 실행할 Tasklet
* @return 생성된 Step 객체
*/
protected Step addStep(String stepName, Tasklet tasklet) {
return new StepBuilder(stepName)
.repository(jobRepository)
.transactionManager(transactionManager)
.tasklet(tasklet)
.build();
}
/**
* 서브클래스에서 구현해야 하는 Tasklet을 생성합니다.
*
* @return 생성된 Tasklet 객체
*/
protected abstract Tasklet createTasklet();
/**
* 배치 작업 그룹을 반환합니다.
*
* @return 배치 작업 그룹 이름
*/
public String group() {
return batchJobInfo.group();
}
/**
* 배치 작업 이름을 반환합니다.
*
* @return 배치 작업 이름
*/
public String jobName() {
return batchJobInfo.jobName();
}
/**
* cron 표현식을 반환합니다.
*
* @return cron 표현식
*/
public String cronExpression() {
return batchJobInfo.cronExpression();
}
}

View File

@@ -1,6 +1,42 @@
package com.spring.infra.batch;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 배치 작업을 위한 설정 클래스입니다.
* <p>
* 이 클래스는 Spring Batch의 배치 처리 기능을 활성화하고,
* JobRegistryBeanPostProcessor를 설정하여 배치 작업을 등록합니다.
* </p>
*
* <p>
* {@code @EnableBatchProcessing} 어노테이션은 Spring Batch의 배치 처리 기능을 활성화합니다.
* 이 어노테이션을 사용하면, 배치 작업을 정의하고 실행하는 데 필요한 기본 구성 요소들이 자동으로 설정됩니다.
* </p>
*/
@Configuration
@EnableBatchProcessing
public class BatchConfig {
/**
* JobRegistryBeanPostProcessor를 생성합니다.
* <p>
* 이 메서드는 JobRegistry를 사용하여 배치 작업을 등록하는
* JobRegistryBeanPostProcessor를 반환합니다.
* </p>
*
* @param jobRegistry JobRegistry 객체
* @return 설정된 JobRegistryBeanPostProcessor 객체
*/
@Bean
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
final var processor = new JobRegistryBeanPostProcessor();
processor.setJobRegistry(jobRegistry);
return processor;
}
}

View File

@@ -0,0 +1,46 @@
package com.spring.infra.batch;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 배치 작업에 대한 메타데이터를 정의하는 어노테이션입니다.
* <p>
* 이 어노테이션은 배치 작업의 그룹, 이름 및 cron 표현식을 설정하는 데 사용됩니다.
* </p>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BatchJobInfo {
/**
* 배치 작업의 그룹을 정의합니다.
* <p>
* 기본값은 "DEFAULT"입니다.
* </p>
*
* @return 배치 작업 그룹 이름
*/
String group() default "DEFAULT";
/**
* 배치 작업의 이름을 정의합니다.
* <p>
* 이 값은 배치 작업을 식별하는 데 사용됩니다.
* </p>
*
* @return 배치 작업 이름
*/
String jobName() default "";
/**
* 배치 작업의 cron 표현식을 정의합니다.
* <p>
* 이 표현식은 배치 작업의 실행 주기를 설정하는 데 사용됩니다.
* </p>
*
* @return cron 표현식
*/
String cronExpression() default "";
}

View File

@@ -8,6 +8,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import com.zaxxer.hikari.HikariDataSource;
@@ -57,7 +58,9 @@ public class PrimaryDataSourceConfig {
@Primary
@Bean(name = DATASOURCE)
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
return new LazyConnectionDataSourceProxy( // 커넥션이 필요한 경우에만 datasource 에서 connection 을 반환
properties.initializeDataSourceBuilder().type(HikariDataSource.class).build()
);
}
}

View File

@@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import com.zaxxer.hikari.HikariDataSource;
@@ -54,7 +55,9 @@ public class SecondaryDataSourceConfig {
*/
@Bean(name = DATASOURCE)
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
return new LazyConnectionDataSourceProxy( // 커넥션이 필요한 경우에만 datasource 에서 connection 을 반환
properties.initializeDataSourceBuilder().type(HikariDataSource.class).build()
);
}
}

View File

@@ -38,7 +38,7 @@ import com.spring.infra.db.orm.mybatis.annotation.PrimaryMapper;
public class PrimaryMybatisConfig {
private static final String BASE_PACKAGE = "com.spring.domain.*.mapper";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto,com.spring.domain.*.entity";
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
private static final String SESSION_FACTORY = "primarySqlSessionFactory";

View File

@@ -37,7 +37,7 @@ import com.spring.infra.db.orm.mybatis.annotation.SecondaryMapper;
public class SecondaryMybatisConfig {
private static final String BASE_PACKAGE = "com.spring.domain.*.mapper";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto";
private static final String ALIASES_PACKAGE = "com.spring.domain.*.dto,com.spring.domain.*.entity";
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
private static final String SESSION_FACTORY = "secondarySqlSessionFactory";

View File

@@ -43,12 +43,12 @@ public @interface QuartzJob {
*
* @return 작업의 이름
*/
String name();
String jobName() default "";
/**
* 작업의 실행 주기를 Cron 표현식으로 지정합니다.
*
* @return Cron 표현식
*/
String cronExpression();
String cronExpression() default "";
}

View File

@@ -1,8 +1,10 @@
package com.spring.infra.quartz;
import java.lang.reflect.Method;
import java.util.Map;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
@@ -17,18 +19,18 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import com.spring.infra.batch.AbstractBatchJob;
import lombok.RequiredArgsConstructor;
/**
* Quartz 작업 등록기 클래스입니다.
*
* <p>이 클래스는 애플리케이션 컨텍스트가 리프레시될 때 실행되며,
* {@link QuartzJob} 어노테이션이 붙은 모든 메소드를 찾아 Quartz 스케줄러에 등록합니다.</p>
*
* <p>주요 기능:</p>
* <ul>
* <li>애플리케이션 컨텍스트 내의 모든 빈을 검사</li>
* <li>QuartzJob 어노테이션이 붙은 메소드 식별</li>
* <li>AbstractBatchJob을 상속받은 클래스 식별</li>
* <li>식별된 메소드를 Quartz 작업으로 등록</li>
* <li>각 작업에 대한 JobDetail 및 Trigger 생성</li>
* </ul>
@@ -38,9 +40,6 @@ import lombok.RequiredArgsConstructor;
*
* @author mindol
* @version 1.0
* @see QuartzJob
* @see ApplicationListener
* @see ContextRefreshedEvent
*/
@Component
@RequiredArgsConstructor
@@ -50,53 +49,50 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
private final ApplicationContext applicationContext;
/**
* 애플리케이션 컨텍스트가 리프레시될 때 호출되는 메소드.
* 모든 빈을 순회하며 QuartzJob 어노테이션이 붙은 메소드를 찾아 Quartz 작업으로 등록합니다.
*
* 애플리케이션 컨텍스트가 리프레시될 때 호출되는 메소드입니다.
* QuartzJob 어노테이션이 붙은 메소드와 AbstractBatchJob을 상속받은 클래스를 등록합니다.
*
* @param event ContextRefreshedEvent 객체
*/
@Override
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
registerQuartzJobs();
registerAbstractJobs();
}
/**
* QuartzJob 어노테이션이 붙은 모든 메소드를 찾아 Quartz 스케줄러에 등록합니다.
*/
private void registerQuartzJobs() {
for (String beanName : applicationContext.getBeanDefinitionNames()) {
Object bean = applicationContext.getBean(beanName);
Class<?> beanClass = bean.getClass();
for (Method method : beanClass.getDeclaredMethods()) {
QuartzJob quartzJobAnnotation = AnnotationUtils.findAnnotation(method, QuartzJob.class);
if (quartzJobAnnotation != null) {
registerQuartzJob(quartzJobAnnotation, method.getName());
try {
JobDetail jobDetail = createJobDetail(quartzJobAnnotation);
Trigger trigger = createTrigger(quartzJobAnnotation, jobDetail);
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new IllegalStateException("Error scheduling Quartz job", e);
}
}
}
}
}
/**
* QuartzJob 어노테이션 정보를 바탕으로 Quartz 작업을 등록합니다.
*
* @param quartzJobAnnotation QuartzJob 어노테이션 객체
* @param jobName 등록할 작업의 이름
*/
private void registerQuartzJob(QuartzJob quartzJobAnnotation, String jobName) {
try {
JobDetail jobDetail = createJobDetail(quartzJobAnnotation, jobName);
Trigger trigger = createTrigger(quartzJobAnnotation, jobDetail);
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException 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) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", jobName);
jobDataMap.put("jobName", quartzJobAnnotation.jobName());
return JobBuilder.newJob(QuartzJobLauncher.class)
.withIdentity(quartzJobAnnotation.name(), quartzJobAnnotation.group())
.withIdentity(quartzJobAnnotation.jobName(), quartzJobAnnotation.group())
.setJobData(jobDataMap)
.storeDurably()
.build();
@@ -112,9 +108,35 @@ public class QuartzJobRegistrar implements ApplicationListener<ContextRefreshedE
private Trigger createTrigger(QuartzJob quartzJobAnnotation, JobDetail jobDetail) {
return TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(quartzJobAnnotation.name() + "Trigger", quartzJobAnnotation.group())
.withIdentity(quartzJobAnnotation.jobName() + "Trigger", quartzJobAnnotation.group())
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJobAnnotation.cronExpression()))
.build();
}
/**
* AbstractBatchJob을 상속받은 모든 클래스를 찾아 Quartz 스케줄러에 등록합니다.
*/
public void registerAbstractJobs() {
Map<String, AbstractBatchJob> batchJobs = applicationContext.getBeansOfType(AbstractBatchJob.class);
for (AbstractBatchJob batchJob : batchJobs.values()) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobName", batchJob.jobName());
JobDetail jobDetail = JobBuilder.newJob(QuartzJobLauncher.class)
.withIdentity(batchJob.jobName(), batchJob.group())
.setJobData(jobDataMap)
.storeDurably()
.build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(batchJob.jobName() + "Trigger", batchJob.group())
.withSchedule(CronScheduleBuilder.cronSchedule(batchJob.cronExpression()))
.build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new IllegalStateException("Error scheduling AbstractBatchJob: " + batchJob.jobName(), e);
}
}
}
}

View File

@@ -6,6 +6,7 @@ import java.util.Arrays;
import java.util.Properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -29,6 +30,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "spring.quartz.properties.org.quartz")
public class QuartzProperties {

View File

@@ -1,6 +1,7 @@
package com.spring.infra.security.jwt;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -12,6 +13,7 @@ import lombok.RequiredArgsConstructor;
* @version 1.0
*/
@Getter
@ConstructorBinding
@ConfigurationProperties(prefix = "jwt")
@RequiredArgsConstructor
public final class JwtProperties {

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.domain.post.mapper.PostMapper">
<insert id="save">
insert
into app_post (content, title)
values (#{post.content}, #{post.title})
</insert>
<select id="findAll" resultType="Post">
select * from app_post;
</select>
</mapper>

View File

@@ -3,8 +3,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.domain.user.mapper.AppUserMaper">
<select id="findByLoginId" resultType="com.spring.domain.user.entity.AppUser">
select *
from app_user
<select id="findByLoginId" resultType="AppUser">
select * from app_user
</select>
</mapper>