first Commit!!!
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@ConfigurationPropertiesScan
|
||||
public class BatchQuartzApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BatchQuartzApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.spring.common.error;
|
||||
|
||||
public class BizException extends RuntimeException {
|
||||
|
||||
public BizException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_EXECUTION")
|
||||
@Getter
|
||||
public class BatchJobExecution {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
private Long jobExecutionId;
|
||||
|
||||
@Column(name = "VERSION")
|
||||
private Long version;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_INSTANCE_ID", nullable = false, foreignKey = @ForeignKey(name = "JOB_INST_EXEC_FK"))
|
||||
private BatchJobInstance batchJobInstance;
|
||||
|
||||
@Column(name = "CREATE_TIME", nullable = false)
|
||||
private Timestamp createTime;
|
||||
|
||||
@Column(name = "START_TIME")
|
||||
private Timestamp startTime;
|
||||
|
||||
@Column(name = "END_TIME")
|
||||
private Timestamp endTime;
|
||||
|
||||
@Column(name = "STATUS", length = 10)
|
||||
private String status;
|
||||
|
||||
@Column(name = "EXIT_CODE", length = 2500)
|
||||
private String exitCode;
|
||||
|
||||
@Column(name = "EXIT_MESSAGE", length = 2500)
|
||||
private String exitMessage;
|
||||
|
||||
@Column(name = "LAST_UPDATED")
|
||||
private Timestamp lastUpdated;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_EXECUTION_CONTEXT")
|
||||
@Getter
|
||||
public class BatchJobExecutionContext {
|
||||
|
||||
@Id
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
private Long jobExecutionId;
|
||||
|
||||
@Column(name = "SHORT_CONTEXT", length = 2500, nullable = false)
|
||||
private String shortContext;
|
||||
|
||||
@Column(name = "SERIALIZED_CONTEXT")
|
||||
private String serializedContext;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_CTX_FK"))
|
||||
private BatchJobExecution batchJobExecution;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_EXECUTION_PARAMS")
|
||||
@Getter
|
||||
public class BatchJobExecutionParams {
|
||||
|
||||
@EmbeddedId
|
||||
private BatchJobExecutionParamsId id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_PARAMS_FK"))
|
||||
private BatchJobExecution jobExecution;
|
||||
|
||||
@Column(name = "PARAMETER_TYPE", nullable = false)
|
||||
private String parameterType;
|
||||
|
||||
@Column(name = "PARAMETER_VALUE", length = 2500)
|
||||
private String parameterValue;
|
||||
|
||||
@Column(name = "IDENTIFYING", nullable = false)
|
||||
private Character identifying;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class BatchJobExecutionParamsId implements Serializable {
|
||||
@Column(name = "JOB_EXECUTION_ID")
|
||||
private Long jobExecutionId;
|
||||
|
||||
@Column(name = "PARAMETER_NAME")
|
||||
private String parameterName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
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 jakarta.persistence.UniqueConstraint;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_JOB_INSTANCE",
|
||||
uniqueConstraints = @UniqueConstraint(name = "JOB_INST_UN", columnNames = {"JOB_NAME", "JOB_KEY"}))
|
||||
@Getter
|
||||
public class BatchJobInstance {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "JOB_INSTANCE_ID")
|
||||
private Long jobInstanceId;
|
||||
|
||||
@Column(name = "VERSION")
|
||||
private Long version;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 100, nullable = false)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_KEY", length = 32, nullable = false)
|
||||
private String jobKey;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_STEP_EXECUTION")
|
||||
@Getter
|
||||
public class BatchStepExecution {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "STEP_EXECUTION_ID")
|
||||
private Long stepExecutionId;
|
||||
|
||||
@Column(name = "VERSION", nullable = false)
|
||||
private Long version;
|
||||
|
||||
@Column(name = "STEP_NAME", length = 100, nullable = false)
|
||||
private String stepName;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "JOB_EXECUTION_ID", nullable = false, foreignKey = @ForeignKey(name = "JOB_EXEC_STEP_FK"))
|
||||
private BatchJobExecution batchJobExecution;
|
||||
|
||||
@Column(name = "CREATE_TIME", nullable = false)
|
||||
private Timestamp createTime;
|
||||
|
||||
@Column(name = "START_TIME")
|
||||
private Timestamp startTime;
|
||||
|
||||
@Column(name = "END_TIME")
|
||||
private Timestamp endTime;
|
||||
|
||||
@Column(name = "STATUS", length = 10)
|
||||
private String status;
|
||||
|
||||
@Column(name = "COMMIT_COUNT")
|
||||
private Long commitCount;
|
||||
|
||||
@Column(name = "READ_COUNT")
|
||||
private Long readCount;
|
||||
|
||||
@Column(name = "FILTER_COUNT")
|
||||
private Long filterCount;
|
||||
|
||||
@Column(name = "WRITE_COUNT")
|
||||
private Long writeCount;
|
||||
|
||||
@Column(name = "READ_SKIP_COUNT")
|
||||
private Long readSkipCount;
|
||||
|
||||
@Column(name = "WRITE_SKIP_COUNT")
|
||||
private Long writeSkipCount;
|
||||
|
||||
@Column(name = "PROCESS_SKIP_COUNT")
|
||||
private Long processSkipCount;
|
||||
|
||||
@Column(name = "ROLLBACK_COUNT")
|
||||
private Long rollbackCount;
|
||||
|
||||
@Column(name = "EXIT_CODE", length = 2500)
|
||||
private String exitCode;
|
||||
|
||||
@Column(name = "EXIT_MESSAGE", length = 2500)
|
||||
private String exitMessage;
|
||||
|
||||
@Column(name = "LAST_UPDATED")
|
||||
private Timestamp lastUpdated;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.spring.domain.batch.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "BATCH_STEP_EXECUTION_CONTEXT")
|
||||
@Getter
|
||||
public class BatchStepExecutionContext {
|
||||
|
||||
@Id
|
||||
@Column(name = "STEP_EXECUTION_ID")
|
||||
private Long stepExecutionId;
|
||||
|
||||
@Column(name = "SHORT_CONTEXT", length = 2500, nullable = false)
|
||||
private String shortContext;
|
||||
|
||||
@Column(name = "SERIALIZED_CONTEXT")
|
||||
private String serializedContext;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "STEP_EXECUTION_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "STEP_EXEC_CTX_FK"))
|
||||
private BatchStepExecution batchStepExecution;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.spring.domain.email.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.IdClass;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@SequenceGenerator(
|
||||
name = "COM_MAILQUE_MST_SEQ",
|
||||
sequenceName = "COM_MAILQUE_MST_SEQ",
|
||||
initialValue = 1,
|
||||
allocationSize = 1
|
||||
)
|
||||
@Entity
|
||||
@Table(name = "COM_MAILQUE_MST")
|
||||
@IdClass(com.spring.domain.email.entity.ComMailqueMst.ComMailqueMstId.class)
|
||||
public class ComMailqueMst {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COM_MAILQUE_MST_SEQ")
|
||||
@Column(name = "MID", nullable = false)
|
||||
private Long mid;
|
||||
|
||||
@Id
|
||||
@Column(name = "SUBID", nullable = false)
|
||||
private Long subid;
|
||||
|
||||
@Column(name = "TID", nullable = false, length = 10)
|
||||
private String tid;
|
||||
|
||||
@Column(name = "MAILTYPENAME", length = 25)
|
||||
private String mailtypename;
|
||||
|
||||
@Column(name = "SNAME", nullable = false, length = 50)
|
||||
private String sname;
|
||||
|
||||
@Column(name = "SMAIL", nullable = false, length = 100)
|
||||
private String smail;
|
||||
|
||||
@Column(name = "SID", length = 50)
|
||||
private String sid;
|
||||
|
||||
@Column(name = "RPOS", nullable = false, length = 1)
|
||||
private Character rpos;
|
||||
|
||||
@Column(name = "QUERY", length = 2000)
|
||||
private String query;
|
||||
|
||||
@Column(name = "CTNOPS", length = 1)
|
||||
private String ctnops;
|
||||
|
||||
@Column(name = "SUBJECT", length = 100)
|
||||
private String subject;
|
||||
|
||||
@Lob
|
||||
@Column(name = "CONTENTS")
|
||||
private String contents;
|
||||
|
||||
@Column(name = "CDATE")
|
||||
private LocalDate cdate;
|
||||
|
||||
@Column(name = "SDATE")
|
||||
private LocalDate sdate;
|
||||
|
||||
@Column(name = "STATUS", length = 1)
|
||||
private String status;
|
||||
|
||||
@Column(name = "DBCODE", length = 10)
|
||||
private String dbcode;
|
||||
|
||||
@Column(name = "CHARSET")
|
||||
private Integer charset;
|
||||
|
||||
@Column(name = "ISSECURE", length = 1)
|
||||
private String issecure;
|
||||
|
||||
@Column(name = "SECURETEMPLATE", length = 50)
|
||||
private String securetemplate;
|
||||
|
||||
@Column(name = "ATTACHFILE01", length = 255)
|
||||
private String attachfile01;
|
||||
|
||||
@Column(name = "ATTACHFILE02", length = 255)
|
||||
private String attachfile02;
|
||||
|
||||
@Column(name = "ATTACHFILE03", length = 255)
|
||||
private String attachfile03;
|
||||
|
||||
@Column(name = "ATTACHFILE04", length = 255)
|
||||
private String attachfile04;
|
||||
|
||||
@Column(name = "ATTACHFILE05", length = 255)
|
||||
private String attachfile05;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class ComMailqueMstId implements Serializable {
|
||||
private Long mid;
|
||||
private Long subid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.spring.domain.email.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Entity
|
||||
@Table(name = "COM_MAILRST_LOG")
|
||||
public class ComMailrstLog {
|
||||
|
||||
@EmbeddedId
|
||||
private ComMailrstLogId id;
|
||||
|
||||
@Column(name = "TID", nullable = false, length = 20)
|
||||
private String tid;
|
||||
|
||||
@Column(name = "RNAME", length = 50)
|
||||
private String rname;
|
||||
|
||||
@Column(name = "SID", length = 50)
|
||||
private String sid;
|
||||
|
||||
@Column(name = "SNAME", nullable = false, length = 50)
|
||||
private String sname;
|
||||
|
||||
@Column(name = "SMAIL", nullable = false, length = 100)
|
||||
private String smail;
|
||||
|
||||
@Column(name = "RCODE", nullable = false, length = 2)
|
||||
private Character rcode;
|
||||
|
||||
@Column(name = "RMSG", length = 100)
|
||||
private String rmsg;
|
||||
|
||||
@Column(name = "STIME", nullable = false)
|
||||
private LocalDate stime;
|
||||
|
||||
@Column(name = "LASTFLG", length = 2)
|
||||
private Character lastflg;
|
||||
|
||||
@Column(name = "RSTIME")
|
||||
private LocalDate rstime;
|
||||
|
||||
@Column(name = "RSCODE", length = 2)
|
||||
private Character rscode;
|
||||
|
||||
@Column(name = "RSMSG", length = 100)
|
||||
private String rsmsg;
|
||||
|
||||
@Column(name = "RSDETMSG", length = 2000)
|
||||
private String rsdetmsg;
|
||||
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@Builder
|
||||
public static class ComMailrstLogId implements Serializable {
|
||||
@Column(name = "MID", nullable = false)
|
||||
private Long mid;
|
||||
|
||||
@Column(name = "SUBID", nullable = false)
|
||||
private Long subid;
|
||||
|
||||
@Column(name = "RMAIL", nullable = false, length = 100)
|
||||
private String rmail;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.spring.domain.post.entity;
|
||||
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.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.RequiredArgsConstructor;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
@Entity
|
||||
@Table(name = "APP_POST")
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Post {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "POST_ID", nullable = false)
|
||||
private final Long postId;
|
||||
|
||||
@Column(name = "TITLE", nullable = false, length = 100)
|
||||
private final String title;
|
||||
|
||||
@Column(name = "CONTENT", nullable = false, length = 2000)
|
||||
private final String content;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.spring.domain.post.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.spring.domain.post.entity.Post;
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.annotation.DatabaseSelector;
|
||||
|
||||
@DatabaseSelector(SecondaryDataSourceConfig.DATABASE)
|
||||
public interface PostRepository extends JpaRepository<Post, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_BLOB_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzBlobTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzBlobTriggersId id;
|
||||
|
||||
@Column(name = "BLOB_DATA")
|
||||
private byte[] blobData;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns({
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
})
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzBlobTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_CALENDARS")
|
||||
@Getter
|
||||
public class QrtzCalendars {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzCalendarsId id;
|
||||
|
||||
@Column(name = "CALENDAR", nullable = false)
|
||||
private byte[] calendar;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzCalendarsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "CALENDAR_NAME", length = 200, nullable = false)
|
||||
private String calendarName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_CRON_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzCronTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzCronTriggersId id;
|
||||
|
||||
@Column(name = "CRON_EXPRESSION", length = 120, nullable = false)
|
||||
private String cronExpression;
|
||||
|
||||
@Column(name = "TIME_ZONE_ID", length = 80)
|
||||
private String timeZoneId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzCronTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_FIRED_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzFiredTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzFiredTriggersId id;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
|
||||
@Column(name = "INSTANCE_NAME", length = 200, nullable = false)
|
||||
private String instanceName;
|
||||
|
||||
@Column(name = "FIRED_TIME", nullable = false)
|
||||
private long firedTime;
|
||||
|
||||
@Column(name = "SCHED_TIME", nullable = false)
|
||||
private long schedTime;
|
||||
|
||||
@Column(name = "PRIORITY", nullable = false)
|
||||
private int priority;
|
||||
|
||||
@Column(name = "STATE", length = 16, nullable = false)
|
||||
private String state;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 200)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_GROUP", length = 200)
|
||||
private String jobGroup;
|
||||
|
||||
@Column(name = "IS_NONCONCURRENT")
|
||||
private Boolean isNonConcurrent;
|
||||
|
||||
@Column(name = "REQUESTS_RECOVERY")
|
||||
private Boolean requestsRecovery;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzFiredTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "ENTRY_ID", length = 95, nullable = false)
|
||||
private String entryId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_JOB_DETAILS")
|
||||
@Getter
|
||||
public class QrtzJobDetails {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzJobDetailsId id;
|
||||
|
||||
@Column(name = "DESCRIPTION", length = 250)
|
||||
private String description;
|
||||
|
||||
@Column(name = "JOB_CLASS_NAME", length = 250, nullable = false)
|
||||
private String jobClassName;
|
||||
|
||||
@Column(name = "IS_DURABLE", nullable = false)
|
||||
private boolean isDurable;
|
||||
|
||||
@Column(name = "IS_NONCONCURRENT", nullable = false)
|
||||
private boolean isNonConcurrent;
|
||||
|
||||
@Column(name = "IS_UPDATE_DATA", nullable = false)
|
||||
private boolean isUpdateData;
|
||||
|
||||
@Column(name = "REQUESTS_RECOVERY", nullable = false)
|
||||
private boolean requestsRecovery;
|
||||
|
||||
@Column(name = "JOB_DATA")
|
||||
private byte[] jobData;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzJobDetailsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "JOB_NAME", length = 200, nullable = false)
|
||||
private String jobName;
|
||||
|
||||
@Column(name = "JOB_GROUP", length = 200, nullable = false)
|
||||
private String jobGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_LOCKS")
|
||||
@Getter
|
||||
public class QrtzLocks {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzLocksId id;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzLocksId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "LOCK_NAME", length = 40, nullable = false)
|
||||
private String lockName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_PAUSED_TRIGGER_GRPS")
|
||||
@Getter
|
||||
public class QrtzPausedTriggerGrps {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzPausedTriggerGrpsId id;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzPausedTriggerGrpsId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_SCHEDULER_STATE")
|
||||
@Getter
|
||||
public class QrtzSchedulerState {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSchedulerStateId id;
|
||||
|
||||
@Column(name = "LAST_CHECKIN_TIME", nullable = false)
|
||||
private long lastCheckinTime;
|
||||
|
||||
@Column(name = "CHECKIN_INTERVAL", nullable = false)
|
||||
private long checkinInterval;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSchedulerStateId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "INSTANCE_NAME", length = 200, nullable = false)
|
||||
private String instanceName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_SIMPLE_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzSimpleTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSimpleTriggersId id;
|
||||
|
||||
@Column(name = "REPEAT_COUNT", nullable = false)
|
||||
private long repeatCount;
|
||||
|
||||
@Column(name = "REPEAT_INTERVAL", nullable = false)
|
||||
private long repeatInterval;
|
||||
|
||||
@Column(name = "TIMES_TRIGGERED", nullable = false)
|
||||
private long timesTriggered;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSimpleTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_SIMPROP_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzSimpropTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzSimpropTriggersId id;
|
||||
|
||||
@Column(name = "STR_PROP_1", length = 512)
|
||||
private String strProp1;
|
||||
|
||||
@Column(name = "STR_PROP_2", length = 512)
|
||||
private String strProp2;
|
||||
|
||||
@Column(name = "STR_PROP_3", length = 512)
|
||||
private String strProp3;
|
||||
|
||||
@Column(name = "INT_PROP_1")
|
||||
private Integer intProp1;
|
||||
|
||||
@Column(name = "INT_PROP_2")
|
||||
private Integer intProp2;
|
||||
|
||||
@Column(name = "LONG_PROP_1")
|
||||
private Long longProp1;
|
||||
|
||||
@Column(name = "LONG_PROP_2")
|
||||
private Long longProp2;
|
||||
|
||||
@Column(name = "DEC_PROP_1", precision = 13, scale = 4)
|
||||
private BigDecimal decProp1;
|
||||
|
||||
@Column(name = "DEC_PROP_2", precision = 13, scale = 4)
|
||||
private BigDecimal decProp2;
|
||||
|
||||
@Column(name = "BOOL_PROP_1")
|
||||
private Boolean boolProp1;
|
||||
|
||||
@Column(name = "BOOL_PROP_2")
|
||||
private Boolean boolProp2;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_NAME", referencedColumnName = "TRIGGER_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "TRIGGER_GROUP", referencedColumnName = "TRIGGER_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS"))
|
||||
private QrtzTriggers qrtzTriggers;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzSimpropTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.spring.domain.quartz.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "QRTZ_TRIGGERS")
|
||||
@Getter
|
||||
public class QrtzTriggers {
|
||||
|
||||
@EmbeddedId
|
||||
private QrtzTriggersId id;
|
||||
|
||||
@Column(name = "DESCRIPTION", length = 250)
|
||||
private String description;
|
||||
|
||||
@Column(name = "NEXT_FIRE_TIME")
|
||||
private Long nextFireTime;
|
||||
|
||||
@Column(name = "PREV_FIRE_TIME")
|
||||
private Long prevFireTime;
|
||||
|
||||
@Column(name = "PRIORITY")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "TRIGGER_STATE", length = 16, nullable = false)
|
||||
private String triggerState;
|
||||
|
||||
@Column(name = "TRIGGER_TYPE", length = 8, nullable = false)
|
||||
private String triggerType;
|
||||
|
||||
@Column(name = "START_TIME", nullable = false)
|
||||
private Long startTime;
|
||||
|
||||
@Column(name = "END_TIME")
|
||||
private Long endTime;
|
||||
|
||||
@Column(name = "CALENDAR_NAME", length = 200)
|
||||
private String calendarName;
|
||||
|
||||
@Column(name = "MISFIRE_INSTR")
|
||||
private Short misfireInstr;
|
||||
|
||||
@Column(name = "JOB_DATA")
|
||||
private byte[] jobData;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumns(value = {
|
||||
@JoinColumn(name = "SCHED_NAME", referencedColumnName = "SCHED_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "JOB_NAME", referencedColumnName = "JOB_NAME", insertable = false, updatable = false),
|
||||
@JoinColumn(name = "JOB_GROUP", referencedColumnName = "JOB_GROUP", insertable = false, updatable = false)
|
||||
}, foreignKey = @ForeignKey(name = "FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS"))
|
||||
private QrtzJobDetails qrtzJobDetails;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public static class QrtzTriggersId implements Serializable {
|
||||
@Column(name = "SCHED_NAME", length = 120, nullable = false)
|
||||
private String schedName;
|
||||
|
||||
@Column(name = "TRIGGER_NAME", length = 200, nullable = false)
|
||||
private String triggerName;
|
||||
|
||||
@Column(name = "TRIGGER_GROUP", length = 200, nullable = false)
|
||||
private String triggerGroup;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.spring.domain.user.api;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.spring.domain.user.dto.SignInRequest;
|
||||
import com.spring.domain.user.service.AuthService;
|
||||
import com.spring.infra.security.jwt.JwtTokenService;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final JwtTokenService jwtTokenService;
|
||||
|
||||
@PostMapping("/auth")
|
||||
public ResponseEntity<?> generateToken(HttpServletResponse response, @RequestBody SignInRequest request) {
|
||||
Authentication auth = authService.getAuthentication(request.getUsername(), request.getPassword());
|
||||
jwtTokenService.generateAccessToken(response, auth);
|
||||
jwtTokenService.generateRefreshToken(response, auth);
|
||||
return ResponseEntity.ok().body(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.spring.domain.user.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class SignInRequest {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.spring.domain.user.entity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "APP_USER")
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class AppUser {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "USER_ID", nullable = false)
|
||||
private final Long userId;
|
||||
|
||||
@Column(name = "LOGIN_ID", nullable = false, length = 50)
|
||||
private final String loginId;
|
||||
|
||||
@Column(name = "PASSWORD", nullable = false, length = 128)
|
||||
private final String password;
|
||||
|
||||
@Column(name = "USER_NAME", nullable = false, length = 50)
|
||||
private final String userName;
|
||||
|
||||
@OneToMany(mappedBy = "appUser", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
private final Set<AppUserRoleMap> appUserRoleMap;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.spring.domain.user.entity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "APP_USER_ROLE")
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class AppUserRole {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "ROLE_ID", nullable = false)
|
||||
private final Long roleId;
|
||||
|
||||
@Column(name = "ROLE_TYPE", nullable = false, length = 10)
|
||||
private final String roleType;
|
||||
|
||||
@OneToMany(mappedBy = "appUserRole", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
private final Set<AppUserRoleMap> appUserRoleMap;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.spring.domain.user.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "APP_USER_ROLE_MAP")
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class AppUserRoleMap {
|
||||
|
||||
@EmbeddedId
|
||||
private final AppUserRoleMapId id;
|
||||
|
||||
@ManyToOne
|
||||
@MapsId("roleId")
|
||||
@JoinColumn(name = "ROLE_ID")
|
||||
private final AppUserRole appUserRole;
|
||||
|
||||
@ManyToOne
|
||||
@MapsId("userId")
|
||||
@JoinColumn(name = "USER_ID")
|
||||
private final AppUser appUser;
|
||||
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static final class AppUserRoleMapId implements Serializable {
|
||||
@Column(name = "ROLE_ID", nullable = false)
|
||||
private final Long roleId;
|
||||
|
||||
@Column(name = "USER_ID", nullable = false)
|
||||
private final Long userId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.spring.domain.user.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.spring.domain.user.entity.AppUser;
|
||||
|
||||
public interface AppUserRepository extends JpaRepository<AppUser, Long> {
|
||||
|
||||
Optional<AppUser> findByLoginId(String loginId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.spring.domain.user.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.spring.domain.user.entity.AppUserRoleMap;
|
||||
import com.spring.domain.user.entity.AppUserRoleMap.AppUserRoleMapId;
|
||||
|
||||
public interface AppUserRoleMapRepository extends JpaRepository<AppUserRoleMap, AppUserRoleMapId> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.spring.domain.user.service;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthService {
|
||||
|
||||
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Authentication getAuthentication(String username, String password) {
|
||||
var authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
return authenticationManagerBuilder.getObject().authenticate(authenticationToken);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.spring.infra.batch;
|
||||
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableBatchProcessing
|
||||
public abstract class AbstractBatchConfig {
|
||||
|
||||
@Autowired
|
||||
protected JobBuilderFactory jobBuilderFactory;
|
||||
|
||||
@Autowired
|
||||
protected StepBuilderFactory stepBuilderFactory;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.spring.infra.db;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
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.context.annotation.Primary;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
@Configuration
|
||||
public class PrimaryDataSourceConfig {
|
||||
|
||||
public static final String DATABASE = "primary";
|
||||
public static final String DATASOURCE = "primaryDataSource";
|
||||
private static final String DATASOURCE_PROPERTIES = "primaryDataSourceProperties";
|
||||
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.primary";
|
||||
|
||||
@Primary
|
||||
@Bean(name = DATASOURCE_PROPERTIES)
|
||||
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
||||
DataSourceProperties dataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(name = DATASOURCE)
|
||||
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
||||
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.spring.infra.db;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
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 com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
@Configuration
|
||||
public class SecondaryDataSourceConfig {
|
||||
|
||||
public static final String DATABASE = "secondary";
|
||||
public static final String DATASOURCE = "secondaryDataSource";
|
||||
private static final String DATASOURCE_PROPERTIES = "secondaryDataSourceProperties";
|
||||
private static final String DATASOURCE_PROPERTIES_PREFIX = "spring.datasource.secondary";
|
||||
|
||||
@Bean(name = DATASOURCE_PROPERTIES)
|
||||
@ConfigurationProperties(prefix = DATASOURCE_PROPERTIES_PREFIX)
|
||||
DataSourceProperties dataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Bean(name = DATASOURCE)
|
||||
DataSource dataSource(@Qualifier(DATASOURCE_PROPERTIES) DataSourceProperties properties) {
|
||||
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.spring.infra.db.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DatabaseSelector {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.spring.infra.db.orm.jpa;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import com.spring.infra.db.PrimaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.jpa.util.DatabaseSelectorFilter;
|
||||
import com.spring.infra.db.orm.jpa.util.EntityScannerConfigurer;
|
||||
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories(
|
||||
basePackages = {PrimaryJpaConfig.BASE_PACKAGE},
|
||||
entityManagerFactoryRef = PrimaryJpaConfig.ENTITY_MANAGER_FACTORY,
|
||||
transactionManagerRef = PrimaryJpaConfig.TRANSACTION_MANAGER,
|
||||
includeFilters = @Filter(type = FilterType.CUSTOM, classes = DatabaseSelectorFilter.class)
|
||||
)
|
||||
public class PrimaryJpaConfig {
|
||||
|
||||
public static final String TRANSACTION_MANAGER = "primaryTransactionManager";
|
||||
private static final String DATABASE_FILTER = "primaryDatabaseFilter";
|
||||
private static final String BASE_PACKAGE = "com.spring.domain";
|
||||
private static final String ENTITY_MANAGER_FACTORY = "primaryEntityManagerFactory";
|
||||
private static final String PERSISTENCE_UNIT = "primaryPersistenceUnit";
|
||||
|
||||
@Primary
|
||||
@Bean(name = DATABASE_FILTER)
|
||||
DatabaseSelectorFilter databaseSelectorFilter() {
|
||||
var filter = new DatabaseSelectorFilter();
|
||||
filter.setDbName(PrimaryDataSourceConfig.DATABASE);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(name = ENTITY_MANAGER_FACTORY)
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
||||
EntityManagerFactoryBuilder builder,
|
||||
@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource
|
||||
) {
|
||||
var entities = EntityScannerConfigurer.scanEntities(BASE_PACKAGE, PrimaryDataSourceConfig.DATABASE);
|
||||
return builder
|
||||
.dataSource(dataSource)
|
||||
.packages(entities.stream().map(BeanDefinition::getBeanClassName).toArray(String[]::new))
|
||||
.persistenceUnit(PERSISTENCE_UNIT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(TRANSACTION_MANAGER)
|
||||
PlatformTransactionManager transactionManager(
|
||||
@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory
|
||||
) {
|
||||
return new JpaTransactionManager(entityManagerFactory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.spring.infra.db.orm.jpa;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.jpa.util.DatabaseSelectorFilter;
|
||||
import com.spring.infra.db.orm.jpa.util.EntityScannerConfigurer;
|
||||
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories(
|
||||
basePackages = {SecondaryJpaConfig.BASE_PACKAGE},
|
||||
entityManagerFactoryRef = SecondaryJpaConfig.ENTITY_MANAGER_FACTORY,
|
||||
transactionManagerRef = SecondaryJpaConfig.TRANSACTION_MANAGER,
|
||||
includeFilters = @Filter(type = FilterType.CUSTOM, classes = DatabaseSelectorFilter.class)
|
||||
)
|
||||
public class SecondaryJpaConfig {
|
||||
|
||||
public static final String TRANSACTION_MANAGER = "secondaryTransactionManager";
|
||||
private static final String DATABASE_FILTER = "secondaryDatabaseFilter";
|
||||
private static final String BASE_PACKAGE = "com.spring.domain";
|
||||
private static final String ENTITY_MANAGER_FACTORY = "secondaryEntityManagerFactory";
|
||||
private static final String PERSISTENCE_UNIT = "secondaryPersistenceUnit";
|
||||
|
||||
@Bean(name = DATABASE_FILTER)
|
||||
DatabaseSelectorFilter databaseSelectorFilter() {
|
||||
var filter = new DatabaseSelectorFilter();
|
||||
filter.setDbName(SecondaryDataSourceConfig.DATABASE);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean(name = ENTITY_MANAGER_FACTORY)
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
||||
EntityManagerFactoryBuilder builder,
|
||||
@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource
|
||||
) {
|
||||
var entities = EntityScannerConfigurer.scanEntities(BASE_PACKAGE, SecondaryDataSourceConfig.DATABASE);
|
||||
return builder
|
||||
.dataSource(dataSource)
|
||||
.packages(entities.stream().map(BeanDefinition::getBeanClassName).toArray(String[]::new))
|
||||
.persistenceUnit(PERSISTENCE_UNIT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean(TRANSACTION_MANAGER)
|
||||
PlatformTransactionManager transactionManager(
|
||||
@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory
|
||||
) {
|
||||
return new JpaTransactionManager(entityManagerFactory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.spring.infra.db.orm.jpa.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import com.spring.infra.db.annotation.DatabaseSelector;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
public class DatabaseSelectorFilter implements TypeFilter {
|
||||
|
||||
@Setter private String dbName;
|
||||
|
||||
@Override
|
||||
public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory)
|
||||
throws IOException {
|
||||
boolean hasAnnotation = metadataReader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
|
||||
if (!hasAnnotation) {
|
||||
return true;
|
||||
} else {
|
||||
return Optional.ofNullable(metadataReader.getAnnotationMetadata()
|
||||
.getAnnotationAttributes(DatabaseSelector.class.getName()))
|
||||
.map(attributes -> dbName.equals(attributes.get("value")))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.spring.infra.db.orm.jpa.util;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import com.spring.infra.db.annotation.DatabaseSelector;
|
||||
|
||||
public class EntityScannerConfigurer {
|
||||
|
||||
public static Set<BeanDefinition> scanEntities(String basePackage, String dbName) {
|
||||
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.addIncludeFilter(new TypeFilter() {
|
||||
@Override
|
||||
public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory) {
|
||||
boolean hasAnnotation = metadataReader.getAnnotationMetadata().hasAnnotation(DatabaseSelector.class.getName());
|
||||
if (!hasAnnotation) {
|
||||
return true;
|
||||
} else {
|
||||
return Optional.ofNullable(metadataReader.getAnnotationMetadata()
|
||||
.getAnnotationAttributes(DatabaseSelector.class.getName()))
|
||||
.map(attributes -> dbName.equals(attributes.get("value")))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return scanner.findCandidateComponents(basePackage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.spring.infra.db.orm.mybatis;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
import com.spring.infra.db.PrimaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.mybatis.util.MybatisBeanNameGenerator;
|
||||
|
||||
@Configuration
|
||||
@MapperScan(
|
||||
basePackages = PrimaryMybatisConfig.BASE_PACKAGE,
|
||||
sqlSessionFactoryRef = PrimaryMybatisConfig.SESSION_FACTORY,
|
||||
nameGenerator = MybatisBeanNameGenerator.class
|
||||
)
|
||||
public class PrimaryMybatisConfig {
|
||||
|
||||
private static final String BEAN_NAME_GENERATOR = "primaryNameGenerator";
|
||||
private static final String BASE_PACKAGE = "com.spring.domain";
|
||||
private static final String ALIASES_PACKAGE = "com.spring.domain.*.*.dto";
|
||||
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
||||
private static final String MYBATIS_CONFIG = "primaryMybatisConfig";
|
||||
private static final String SESSION_FACTORY = "primarySqlSessionFactory";
|
||||
|
||||
@Primary
|
||||
@Bean(name = BEAN_NAME_GENERATOR)
|
||||
MybatisBeanNameGenerator mybatisBeanNameGenerator() {
|
||||
return new MybatisBeanNameGenerator(PrimaryDataSourceConfig.DATABASE);
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(name = MYBATIS_CONFIG)
|
||||
org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
||||
var configuration = new org.apache.ibatis.session.Configuration();
|
||||
configuration.setMapUnderscoreToCamelCase(true);
|
||||
configuration.setCallSettersOnNulls(true);
|
||||
configuration.setReturnInstanceForEmptyRow(true);
|
||||
configuration.setJdbcTypeForNull(JdbcType.NULL);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean(name = SESSION_FACTORY)
|
||||
SqlSessionFactory sqlSessionFactory(@Qualifier(PrimaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
||||
var sessionFactory = new SqlSessionFactoryBean();
|
||||
sessionFactory.setConfiguration(mybatisConfiguration());
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_RESOURCES));
|
||||
sessionFactory.setTypeAliasesPackage(ALIASES_PACKAGE);
|
||||
sessionFactory.setVfs(SpringBootVFS.class);
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.spring.infra.db.orm.mybatis;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
import com.spring.infra.db.SecondaryDataSourceConfig;
|
||||
import com.spring.infra.db.orm.mybatis.util.MybatisBeanNameGenerator;
|
||||
|
||||
@Configuration
|
||||
@MapperScan(
|
||||
basePackages = SecondaryMybatisConfig.BASE_PACKAGE,
|
||||
sqlSessionFactoryRef = SecondaryMybatisConfig.SESSION_FACTORY,
|
||||
nameGenerator = MybatisBeanNameGenerator.class
|
||||
)
|
||||
public class SecondaryMybatisConfig {
|
||||
|
||||
private static final String BEAN_NAME_GENERATOR = "secondaryNameGenerator";
|
||||
private static final String BASE_PACKAGE = "com.spring.domain";
|
||||
private static final String ALIASES_PACKAGE = "com.spring.domain.*.*.dto";
|
||||
private static final String MAPPER_RESOURCES = "classpath:mapper/**/*.xml";
|
||||
private static final String MYBATIS_CONFIG = "secondaryMybatisConfig";
|
||||
private static final String SESSION_FACTORY = "secondarySqlSessionFactory";
|
||||
|
||||
@Bean(name = BEAN_NAME_GENERATOR)
|
||||
MybatisBeanNameGenerator mybatisBeanNameGenerator() {
|
||||
return new MybatisBeanNameGenerator(SecondaryDataSourceConfig.DATABASE);
|
||||
}
|
||||
|
||||
@Bean(name = MYBATIS_CONFIG)
|
||||
org.apache.ibatis.session.Configuration mybatisConfiguration() {
|
||||
var configuration = new org.apache.ibatis.session.Configuration();
|
||||
configuration.setMapUnderscoreToCamelCase(true);
|
||||
configuration.setCallSettersOnNulls(true);
|
||||
configuration.setReturnInstanceForEmptyRow(true);
|
||||
configuration.setJdbcTypeForNull(JdbcType.NULL);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Bean(name = SESSION_FACTORY)
|
||||
SqlSessionFactory sqlSessionFactory(@Qualifier(SecondaryDataSourceConfig.DATASOURCE) DataSource dataSource) throws Exception {
|
||||
var sessionFactory = new SqlSessionFactoryBean();
|
||||
sessionFactory.setConfiguration(mybatisConfiguration());
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_RESOURCES));
|
||||
sessionFactory.setTypeAliasesPackage(ALIASES_PACKAGE);
|
||||
sessionFactory.setVfs(SpringBootVFS.class);
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.spring.infra.db.orm.mybatis.util;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import com.spring.infra.db.annotation.DatabaseSelector;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MybatisBeanNameGenerator extends AnnotationBeanNameGenerator {
|
||||
|
||||
private final String dbName;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String generateBeanName(@NonNull BeanDefinition definition, @NonNull BeanDefinitionRegistry registry) {
|
||||
return Optional.ofNullable(definition.getBeanClassName())
|
||||
.flatMap(className -> {
|
||||
try {
|
||||
var beanClass = Class.forName(className);
|
||||
var annotation = beanClass.getAnnotation(DatabaseSelector.class);
|
||||
if (annotation == null) {
|
||||
return Optional.of(super.generateBeanName(definition, registry));
|
||||
}
|
||||
if (dbName.equals(annotation.value())) {
|
||||
return Optional.of(super.generateBeanName(definition, registry));
|
||||
}
|
||||
return Optional.empty();
|
||||
} catch (ClassNotFoundException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.spring.infra.quartz;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.quartz.spi.JobFactory;
|
||||
import org.quartz.spi.TriggerFiredBundle;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
|
||||
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class QuartzConfig {
|
||||
|
||||
private final QuartzProperties quartzProperties;
|
||||
private final DataSource dataSource;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
|
||||
/**
|
||||
* JobRegistry 에 Job 을 자동으로 등록하기 위한 설정.
|
||||
*
|
||||
* @param jobRegistry ths Spring Batch Job Registry
|
||||
* @return JobRegistry BeanPostProcessor
|
||||
*/
|
||||
// @Bean
|
||||
// JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
|
||||
// var jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
|
||||
// jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
|
||||
// return jobRegistryBeanPostProcessor;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Quartz Schedule Job 에 의존성 주입
|
||||
*
|
||||
* @param beanFactory application context beanFactory
|
||||
* @return the job factory
|
||||
*/
|
||||
@Bean
|
||||
JobFactory jobFactory(AutowireCapableBeanFactory beanFactory) {
|
||||
return new SpringBeanJobFactory(){
|
||||
@Override
|
||||
protected @NonNull Object createJobInstance(@NonNull final TriggerFiredBundle bundle) throws Exception {
|
||||
var job = super.createJobInstance(bundle);
|
||||
beanFactory.autowireBean(job);
|
||||
return job;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler 전체를 관리하는 Manager.
|
||||
*
|
||||
* @param datasource Spring datasource
|
||||
* @param quartzProperties quartz config
|
||||
* @return the scheduler factory bean
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Bean
|
||||
SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) throws Exception {
|
||||
var factory = new SchedulerFactoryBean();
|
||||
factory.setSchedulerName("SampleProject-0.0.1");
|
||||
factory.setQuartzProperties(quartzProperties.toProperties());
|
||||
factory.setOverwriteExistingJobs(true); //Job Detail 데이터 Overwrite 유무
|
||||
factory.setDataSource(dataSource); //Schedule 관리를 Spring Datasource 에 위임
|
||||
factory.setTransactionManager(transactionManager);
|
||||
factory.setJobFactory(jobFactory);
|
||||
factory.setAutoStartup(true);
|
||||
factory.setWaitForJobsToCompleteOnShutdown(true);
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.spring.infra.quartz;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface QuartzJob {
|
||||
String name();
|
||||
String cronExpression();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.spring.infra.quartz;
|
||||
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.configuration.JobLocator;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
public class QuartzJobLauncher extends QuartzJobBean {
|
||||
|
||||
private String jobName;
|
||||
private JobLauncher jobLauncher;
|
||||
private JobLocator jobLocator;
|
||||
|
||||
public void setJobName(String jobName) {
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
public void setJobLauncher(JobLauncher jobLauncher) {
|
||||
this.jobLauncher = jobLauncher;
|
||||
}
|
||||
|
||||
public void setJobLocator(JobLocator jobLocator) {
|
||||
this.jobLocator = jobLocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeInternal(@NonNull JobExecutionContext context) throws JobExecutionException {
|
||||
try {
|
||||
Job job = jobLocator.getJob(jobName);
|
||||
JobParameters params = new JobParametersBuilder()
|
||||
.addString("JobID", String.valueOf(System.currentTimeMillis()))
|
||||
.toJobParameters();
|
||||
jobLauncher.run(job, params);
|
||||
} catch (Exception e) {
|
||||
throw new JobExecutionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.spring.infra.quartz;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.springframework.batch.core.configuration.JobLocator;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class QuartzJobRegistrar implements BeanPostProcessor, ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private Scheduler scheduler;
|
||||
|
||||
public QuartzJobRegistrar(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
|
||||
Class<?> beanClass = bean.getClass();
|
||||
for (Method method : beanClass.getMethods()) {
|
||||
QuartzJob quartzJobAnnotation = method.getAnnotation(QuartzJob.class);
|
||||
if (quartzJobAnnotation != null) {
|
||||
registerQuartzJob(quartzJobAnnotation, method.getName());
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
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 RuntimeException("Error scheduling Quartz job", e);
|
||||
}
|
||||
}
|
||||
|
||||
private JobDetail createJobDetail(QuartzJob quartzJobAnnotation, String jobName) {
|
||||
JobDataMap jobDataMap = new JobDataMap();
|
||||
jobDataMap.put("jobName", jobName);
|
||||
jobDataMap.put("jobLauncher", applicationContext.getBean(JobLauncher.class));
|
||||
jobDataMap.put("jobLocator", applicationContext.getBean(JobLocator.class));
|
||||
|
||||
return JobBuilder.newJob(QuartzJobLauncher.class)
|
||||
.withIdentity(quartzJobAnnotation.name())
|
||||
.setJobData(jobDataMap)
|
||||
.storeDurably()
|
||||
.build();
|
||||
}
|
||||
|
||||
private Trigger createTrigger(QuartzJob quartzJobAnnotation, JobDetail jobDetail) {
|
||||
return TriggerBuilder.newTrigger()
|
||||
.forJob(jobDetail)
|
||||
.withIdentity(quartzJobAnnotation.name() + "Trigger")
|
||||
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJobAnnotation.cronExpression()))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.spring.infra.quartz;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@ConfigurationProperties(prefix = "spring.quartz.properties.org.quartz")
|
||||
public class QuartzProperties {
|
||||
|
||||
private static final String PREFIX = "org.quartz";
|
||||
|
||||
private final JobStore jobStore;
|
||||
private final Scheduler scheduler;
|
||||
private final ThreadPool threadPool;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class JobStore {
|
||||
private final String tablePrefix;
|
||||
private final String isClustered;
|
||||
private final int clusterCheckinInterval;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class Scheduler {
|
||||
private final String instanceId;
|
||||
private final String instanceName;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class ThreadPool {
|
||||
private final int threadCount;
|
||||
private final int threadPriority;
|
||||
}
|
||||
|
||||
public Properties toProperties() {
|
||||
Properties properties = new Properties();
|
||||
addProperties(PREFIX, this, properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void addProperties(String prefix, Object object, Properties properties) {
|
||||
Arrays.stream(object.getClass().getDeclaredFields())
|
||||
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
||||
.forEach(field -> {
|
||||
setProperties(prefix, object, properties, field);
|
||||
});
|
||||
}
|
||||
|
||||
private void setProperties(String prefix, Object object, Properties properties, Field field) {
|
||||
try {
|
||||
Object value = field.get(object);
|
||||
if (value != null) {
|
||||
String fieldName = field.getName();
|
||||
String key = prefix + "." + fieldName;
|
||||
if (isSimpleType(field.getType())) {
|
||||
properties.setProperty(key, String.valueOf(value));
|
||||
} else {
|
||||
addProperties(key, value, properties);
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("Error getting property value for {}", field.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSimpleType(Class<?> type) {
|
||||
return type.isPrimitive()
|
||||
|| String.class == type
|
||||
|| Number.class.isAssignableFrom(type)
|
||||
|| Boolean.class == type
|
||||
|| Character.class == type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.spring.infra.security.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import com.spring.infra.security.filter.JwtAuthenticationFilter;
|
||||
import com.spring.infra.security.handler.JwtAccessDeniedHandler;
|
||||
import com.spring.infra.security.handler.JwtAuthenticationEntryPoint;
|
||||
import com.spring.infra.security.jwt.JwtTokenService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
public static final String PERMITTED_URI[] = {"/api/auth/**", "/signIn"};
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(
|
||||
HttpSecurity http,
|
||||
JwtTokenService tokenService,
|
||||
JwtAuthenticationEntryPoint authenticationEntryPoint,
|
||||
JwtAccessDeniedHandler accessDeniedHandler) throws Exception
|
||||
{
|
||||
http
|
||||
.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin))
|
||||
.csrf(CsrfConfigurer::disable)
|
||||
.httpBasic(HttpBasicConfigurer::disable)
|
||||
.formLogin(FormLoginConfigurer::disable)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(PERMITTED_URI).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.logout((logout) -> logout
|
||||
.logoutSuccessUrl("/signIn")
|
||||
.invalidateHttpSession(true))
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.addFilterBefore(new JwtAuthenticationFilter(tokenService), UsernamePasswordAuthenticationFilter.class)
|
||||
.exceptionHandling(ex -> ex.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler));
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebSecurityCustomizer ignoringCustomizer() {
|
||||
return (web) -> web.ignoring().requestMatchers("/h2-console/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.spring.infra.security.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.spring.domain.user.entity.AppUser;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class UserPrincipal implements UserDetails {
|
||||
|
||||
private final AppUser appUser;
|
||||
|
||||
public static UserPrincipal valueOf(AppUser appUser) {
|
||||
return new UserPrincipal(appUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return appUser.getAppUserRoleMap().stream()
|
||||
.map(role -> role.getAppUserRole().getRoleType())
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return appUser.getLoginId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.spring.infra.security.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import com.spring.infra.security.jwt.JwtTokenRule;
|
||||
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;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenService jwtTokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
String accessToken = jwtTokenService.resolveTokenFromCookie(request, JwtTokenRule.ACCESS_PREFIX);
|
||||
if (jwtTokenService.validateAccessToken(accessToken)) {
|
||||
setAuthenticationToContext(accessToken);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String refreshToken = jwtTokenService.resolveTokenFromCookie(request, JwtTokenRule.REFRESH_PREFIX);
|
||||
if (jwtTokenService.validateRefreshToken(refreshToken)) {
|
||||
Authentication authentication = jwtTokenService.getAuthentication(refreshToken);
|
||||
String reissuedAccessToken = jwtTokenService.generateAccessToken(response, authentication);
|
||||
jwtTokenService.generateRefreshToken(response, authentication);
|
||||
setAuthenticationToContext(reissuedAccessToken);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
jwtTokenService.deleteCookie(response);
|
||||
|
||||
}
|
||||
|
||||
private void setAuthenticationToContext(String accessToken) {
|
||||
Authentication authentication = jwtTokenService.getAuthentication(accessToken);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.spring.infra.security.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 유저 정보는 있으나 자원에 접근할 수 있는 권한이 없는 경우 : SC_FORBIDDEN (403) 응답
|
||||
*/
|
||||
@Component
|
||||
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.spring.infra.security.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 유저 정보 없이 접근한 경우 : SC_UNAUTHORIZED (401) 응답
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@ConfigurationProperties(prefix = "jwt")
|
||||
@RequiredArgsConstructor
|
||||
public final class JwtProperties {
|
||||
|
||||
private final TokenProperties accessToken;
|
||||
private final TokenProperties refreshToken;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class TokenProperties {
|
||||
private final String secret;
|
||||
private final long expiration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtTokenGenerator {
|
||||
|
||||
private final JwtProperties jwtProperties;
|
||||
|
||||
public String generateAccessToken(Authentication authentication) {
|
||||
return Jwts.builder()
|
||||
.setHeader(createHeader())
|
||||
.setClaims(createClaims(authentication))
|
||||
.setSubject(authentication.getName())
|
||||
.setExpiration(Date.from(Instant.now().plus(jwtProperties.getAccessToken().getExpiration(), ChronoUnit.HOURS)))
|
||||
.signWith(new SecretKeySpec(jwtProperties.getAccessToken().getSecret().getBytes(), SignatureAlgorithm.HS512.getJcaName()))
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String generateRefreshToken(Authentication authentication) {
|
||||
return Jwts.builder()
|
||||
.setHeader(createHeader())
|
||||
.setSubject(authentication.getName())
|
||||
.setExpiration(Date.from(Instant.now().plus(jwtProperties.getRefreshToken().getExpiration(), ChronoUnit.HOURS)))
|
||||
.signWith(new SecretKeySpec(jwtProperties.getRefreshToken().getSecret().getBytes(), SignatureAlgorithm.HS512.getJcaName()))
|
||||
.compact();
|
||||
}
|
||||
|
||||
private Map<String, Object> createHeader() {
|
||||
Map<String, Object> header = new HashMap<>();
|
||||
header.put("typ", "JWT");
|
||||
header.put("alg", "HS512");
|
||||
return header;
|
||||
}
|
||||
|
||||
private Map<String, Object> createClaims(Authentication authentication) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(JwtTokenRule.AUTHORITIES_KEY.getValue(), authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList()));
|
||||
return claims;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum JwtTokenRule {
|
||||
|
||||
JWT_ISSUE_HEADER("Set-Cookie"),
|
||||
JWT_RESOLVE_HEADER("Cookie"),
|
||||
ACCESS_PREFIX("access"),
|
||||
REFRESH_PREFIX("refresh"),
|
||||
BEARER_PREFIX("Bearer "),
|
||||
AUTHORITIES_KEY("auth");
|
||||
|
||||
private final String value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import java.security.Key;
|
||||
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.spring.infra.security.service.UserPrincipalService;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@Service
|
||||
public class JwtTokenService {
|
||||
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
private final JwtTokenGenerator jwtTokenGenerator;
|
||||
private final UserPrincipalService userPrincipalService;
|
||||
|
||||
private final Key ACCESS_SECRET_KEY;
|
||||
private final Key REFRESH_SECRET_KEY;
|
||||
private final long ACCESS_EXPIRATION;
|
||||
private final long REFRESH_EXPIRATION;
|
||||
|
||||
public JwtTokenService(
|
||||
JwtTokenUtil jwtTokenUtil,
|
||||
JwtTokenGenerator jwtTokenGenerator,
|
||||
UserPrincipalService userPrincipalService,
|
||||
JwtProperties jwtProperties
|
||||
) {
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
this.jwtTokenGenerator = jwtTokenGenerator;
|
||||
this.userPrincipalService = userPrincipalService;
|
||||
this.ACCESS_SECRET_KEY = jwtTokenUtil.getSigningKey(jwtProperties.getAccessToken().getSecret());
|
||||
this.REFRESH_SECRET_KEY = jwtTokenUtil.getSigningKey(jwtProperties.getRefreshToken().getSecret());
|
||||
this.ACCESS_EXPIRATION = jwtProperties.getAccessToken().getExpiration();
|
||||
this.REFRESH_EXPIRATION = jwtProperties.getRefreshToken().getExpiration();
|
||||
}
|
||||
|
||||
public String generateAccessToken(HttpServletResponse response, Authentication authentication) {
|
||||
String accessToken = jwtTokenGenerator.generateAccessToken(authentication);
|
||||
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000);
|
||||
response.addHeader(JwtTokenRule.JWT_ISSUE_HEADER.getValue(), cookie.toString());
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String generateRefreshToken(HttpServletResponse response, Authentication authentication) {
|
||||
String refreshToken = jwtTokenGenerator.generateRefreshToken(authentication);
|
||||
ResponseCookie cookie = setTokenToCookie(JwtTokenRule.REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000);
|
||||
response.addHeader(JwtTokenRule.JWT_ISSUE_HEADER.getValue(), cookie.toString());
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long maxAgeSeconds) {
|
||||
return ResponseCookie.from(tokenPrefix, token)
|
||||
.path("/")
|
||||
.maxAge(maxAgeSeconds)
|
||||
.httpOnly(true)
|
||||
.sameSite("None")
|
||||
.secure(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public boolean validateAccessToken(String token) {
|
||||
return jwtTokenUtil.getTokenStatus(token, ACCESS_SECRET_KEY) == JwtTokenStatus.AUTHENTICATED;
|
||||
}
|
||||
|
||||
public boolean validateRefreshToken(String token) {
|
||||
return jwtTokenUtil.getTokenStatus(token, REFRESH_SECRET_KEY) == JwtTokenStatus.AUTHENTICATED;
|
||||
}
|
||||
|
||||
public String resolveTokenFromCookie(HttpServletRequest request, JwtTokenRule tokenPrefix) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies == null) {
|
||||
throw new RuntimeException("JWT_TOKEN_NOT_FOUND");
|
||||
}
|
||||
return jwtTokenUtil.resolveTokenFromCookie(cookies, tokenPrefix);
|
||||
}
|
||||
|
||||
public Authentication getAuthentication(String token) {
|
||||
UserDetails principal = userPrincipalService.loadUserByUsername(getUserPk(token, ACCESS_SECRET_KEY));
|
||||
return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities());
|
||||
}
|
||||
|
||||
private String getUserPk(String token, Key secretKey) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
public void deleteCookie(HttpServletResponse response) {
|
||||
Cookie accessCookie = jwtTokenUtil.resetToken(JwtTokenRule.ACCESS_PREFIX);
|
||||
Cookie refreshCookie = jwtTokenUtil.resetToken(JwtTokenRule.REFRESH_PREFIX);
|
||||
response.addCookie(accessCookie);
|
||||
response.addCookie(refreshCookie);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum JwtTokenStatus {
|
||||
AUTHENTICATED,
|
||||
EXPIRED,
|
||||
INVALID
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.spring.infra.security.jwt;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtTokenUtil {
|
||||
|
||||
public JwtTokenStatus getTokenStatus(String token, Key secretKey) {
|
||||
try {
|
||||
Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
return JwtTokenStatus.AUTHENTICATED;
|
||||
} catch (ExpiredJwtException | IllegalArgumentException e) {
|
||||
log.error("만료된 JWT 토큰입니다.");
|
||||
return JwtTokenStatus.EXPIRED;
|
||||
} catch (JwtException e) {
|
||||
log.error("JWT 토큰이 잘못되었습니다.");
|
||||
return JwtTokenStatus.INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
public String resolveTokenFromCookie(Cookie[] cookies, JwtTokenRule tokenPrefix) {
|
||||
return Arrays.stream(cookies)
|
||||
.filter(cookie -> cookie.getName().equals(tokenPrefix.getValue()))
|
||||
.findFirst()
|
||||
.map(Cookie::getValue)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public Key getSigningKey(String secretKey) {
|
||||
String encodedKey = encodeToBase64(secretKey);
|
||||
return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String encodeToBase64(String secretKey) {
|
||||
return Base64.getEncoder().encodeToString(secretKey.getBytes());
|
||||
}
|
||||
|
||||
public Cookie resetToken(JwtTokenRule tokenPrefix) {
|
||||
Cookie cookie = new Cookie(tokenPrefix.getValue(), null);
|
||||
cookie.setMaxAge(0);
|
||||
cookie.setPath("/");
|
||||
return cookie;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.spring.infra.security.service;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.spring.domain.user.entity.AppUser;
|
||||
import com.spring.domain.user.repository.AppUserRepository;
|
||||
import com.spring.infra.security.domain.UserPrincipal;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserPrincipalService implements UserDetailsService {
|
||||
|
||||
private final AppUserRepository appUserRepository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
AppUser user = appUserRepository.findByLoginId(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("NOT FOUND USER"));
|
||||
return UserPrincipal.valueOf(user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
{"properties": [
|
||||
{
|
||||
"name": "jwt.secret",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'jwt.secret'"
|
||||
},
|
||||
{
|
||||
"name": "jwt.access-token.expiration",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'jwt.access-token.expiration'"
|
||||
},
|
||||
{
|
||||
"name": "jwt.refresh-token.expiration",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'jwt.refresh-token.expiration'"
|
||||
},
|
||||
{
|
||||
"name": "jwt.access-token.secret",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'jwt.access-token.secret'"
|
||||
},
|
||||
{
|
||||
"name": "jwt.refresh-token.secret",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'jwt.refresh-token.secret'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.driver-class-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.driver-class-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.url",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.url'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.username",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.username'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.password",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.password'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.app.hikari.pool-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.app.hikari.pool-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.app.hikari.maximum-pool-size",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.app.hikari.maximum-pool-size'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.app.hikari.minimum-idle",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.app.hikari.minimum-idle'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.hikari.pool-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.hikari.pool-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.hikari.maximum-pool-size",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.hikari.maximum-pool-size'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.mob.hikari.minimum-idle",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.mob.hikari.minimum-idle'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.driver-class-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.driver-class-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.url",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.url'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.username",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.username'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.password",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.password'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.hikari.pool-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.hikari.pool-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.hikari.maximum-pool-size",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.hikari.maximum-pool-size'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.primary.hikari.minimum-idle",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.primary.hikari.minimum-idle'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.driver-class-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.driver-class-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.url",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.url'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.username",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.username'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.password",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.password'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.hikari.pool-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.hikari.pool-name'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.hikari.maximum-pool-size",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.hikari.maximum-pool-size'"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.secondary.hikari.minimum-idle",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'spring.datasource.secondary.hikari.minimum-idle'"
|
||||
}
|
||||
]}
|
||||
85
batch-quartz/src/main/resources/application.yml
Normal file
85
batch-quartz/src/main/resources/application.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: spring-batch-quartz
|
||||
datasource:
|
||||
primary:
|
||||
driver-class-name: org.h2.Driver
|
||||
url: 'jdbc:h2:mem:app'
|
||||
username: mindol1004
|
||||
password: 1111
|
||||
hikari:
|
||||
pool-name: HikariPool-1
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
secondary:
|
||||
driver-class-name: org.h2.Driver
|
||||
url: 'jdbc:h2:mem:mob'
|
||||
username: mindol1004
|
||||
password: 1111
|
||||
hikari:
|
||||
pool-name: HikariPool-2
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
# sql:
|
||||
# init:
|
||||
# mode: always
|
||||
# schema-locations:
|
||||
# - classpath:batch-schema.sql
|
||||
# - classpath:quartz-schema.sql
|
||||
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.H2Dialect
|
||||
hibernate:
|
||||
ddl-auto: create
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.H2Dialect
|
||||
"[format_sql]": true # 쿼리 로그 포맷 (정렬)
|
||||
"[show_sql]": true # 쿼리 로그 출력
|
||||
naming:
|
||||
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
||||
show-sql: true
|
||||
|
||||
batch:
|
||||
jdbc:
|
||||
initialize-schema: never
|
||||
|
||||
quartz:
|
||||
jdbc:
|
||||
initialize-schema: never
|
||||
wait-for-jobs-to-complete-on-shutdown: true
|
||||
job-store-type: jdbc
|
||||
properties:
|
||||
org:
|
||||
quartz:
|
||||
scheduler:
|
||||
instanceName: BatchQuartzScheduler
|
||||
instanceId: AUTO
|
||||
jobStore:
|
||||
class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
|
||||
tablePrefix: QRTZ_
|
||||
isClustered: true
|
||||
clusterCheckinInterval: 20000
|
||||
threadPool:
|
||||
class: org.quartz.simpl.SimpleThreadPool
|
||||
threadCount: 10
|
||||
threadPriority: 5
|
||||
|
||||
h2:
|
||||
console: # H2 DB를 웹에서 관리할 수 있는 기능
|
||||
enabled: true # H2 Console 사용 여부
|
||||
path: /h2-console # H2 Console 접속 주소
|
||||
settings:
|
||||
web-allow-others: true
|
||||
|
||||
jwt:
|
||||
access-token:
|
||||
secret: bnhjdXMyLjAtcGxhdGZvcm0tcHJvamVjdC13aXRoLXNwcmluZy1ib290
|
||||
expiration: 900 # 15분
|
||||
refresh-token:
|
||||
secret: bnhjdXMyLjAtcGxhdGZvcm0tcHJvamVjdC13aXRoLXNwcmluZy1ib290
|
||||
expiration: 604800 # 7일
|
||||
78
batch-quartz/src/main/resources/batch-schema.sql
Normal file
78
batch-quartz/src/main/resources/batch-schema.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- Batch
|
||||
|
||||
CREATE TABLE BATCH_JOB_INSTANCE (
|
||||
JOB_INSTANCE_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT ,
|
||||
JOB_NAME VARCHAR(100) NOT NULL,
|
||||
JOB_KEY VARCHAR(32) NOT NULL,
|
||||
constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
|
||||
) ;
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION (
|
||||
JOB_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT ,
|
||||
JOB_INSTANCE_ID BIGINT NOT NULL,
|
||||
CREATE_TIME TIMESTAMP(9) NOT NULL,
|
||||
START_TIME TIMESTAMP(9) DEFAULT NULL ,
|
||||
END_TIME TIMESTAMP(9) DEFAULT NULL ,
|
||||
STATUS VARCHAR(10) ,
|
||||
EXIT_CODE VARCHAR(2500) ,
|
||||
EXIT_MESSAGE VARCHAR(2500) ,
|
||||
LAST_UPDATED TIMESTAMP(9),
|
||||
constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
|
||||
references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION_PARAMS (
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL ,
|
||||
PARAMETER_NAME VARCHAR(100) NOT NULL ,
|
||||
PARAMETER_TYPE VARCHAR(100) NOT NULL ,
|
||||
PARAMETER_VALUE VARCHAR(2500) ,
|
||||
IDENTIFYING CHAR(1) NOT NULL ,
|
||||
constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
|
||||
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION (
|
||||
STEP_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
|
||||
VERSION BIGINT NOT NULL,
|
||||
STEP_NAME VARCHAR(100) NOT NULL,
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL,
|
||||
CREATE_TIME TIMESTAMP(9) NOT NULL,
|
||||
START_TIME TIMESTAMP(9) DEFAULT NULL ,
|
||||
END_TIME TIMESTAMP(9) DEFAULT NULL ,
|
||||
STATUS VARCHAR(10) ,
|
||||
COMMIT_COUNT BIGINT ,
|
||||
READ_COUNT BIGINT ,
|
||||
FILTER_COUNT BIGINT ,
|
||||
WRITE_COUNT BIGINT ,
|
||||
READ_SKIP_COUNT BIGINT ,
|
||||
WRITE_SKIP_COUNT BIGINT ,
|
||||
PROCESS_SKIP_COUNT BIGINT ,
|
||||
ROLLBACK_COUNT BIGINT ,
|
||||
EXIT_CODE VARCHAR(2500) ,
|
||||
EXIT_MESSAGE VARCHAR(2500) ,
|
||||
LAST_UPDATED TIMESTAMP(9),
|
||||
constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
|
||||
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT (
|
||||
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
|
||||
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
|
||||
SERIALIZED_CONTEXT LONGVARCHAR ,
|
||||
constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
|
||||
references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT (
|
||||
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
|
||||
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
|
||||
SERIALIZED_CONTEXT LONGVARCHAR ,
|
||||
constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
|
||||
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
|
||||
) ;
|
||||
|
||||
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
|
||||
CREATE SEQUENCE BATCH_JOB_SEQ;
|
||||
238
batch-quartz/src/main/resources/quartz-schema.sql
Normal file
238
batch-quartz/src/main/resources/quartz-schema.sql
Normal file
@@ -0,0 +1,238 @@
|
||||
CREATE TABLE QRTZ_CALENDARS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
CALENDAR_NAME VARCHAR (200) NOT NULL ,
|
||||
CALENDAR IMAGE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_CRON_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||
CRON_EXPRESSION VARCHAR (120) NOT NULL ,
|
||||
TIME_ZONE_ID VARCHAR (80)
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_FIRED_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
ENTRY_ID VARCHAR (95) NOT NULL ,
|
||||
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||
INSTANCE_NAME VARCHAR (200) NOT NULL ,
|
||||
FIRED_TIME BIGINT NOT NULL ,
|
||||
SCHED_TIME BIGINT NOT NULL ,
|
||||
PRIORITY INTEGER NOT NULL ,
|
||||
STATE VARCHAR (16) NOT NULL,
|
||||
JOB_NAME VARCHAR (200) NULL ,
|
||||
JOB_GROUP VARCHAR (200) NULL ,
|
||||
IS_NONCONCURRENT BOOLEAN NULL ,
|
||||
REQUESTS_RECOVERY BOOLEAN NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_SCHEDULER_STATE (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
INSTANCE_NAME VARCHAR (200) NOT NULL ,
|
||||
LAST_CHECKIN_TIME BIGINT NOT NULL ,
|
||||
CHECKIN_INTERVAL BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_LOCKS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
LOCK_NAME VARCHAR (40) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_JOB_DETAILS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
JOB_NAME VARCHAR (200) NOT NULL ,
|
||||
JOB_GROUP VARCHAR (200) NOT NULL ,
|
||||
DESCRIPTION VARCHAR (250) NULL ,
|
||||
JOB_CLASS_NAME VARCHAR (250) NOT NULL ,
|
||||
IS_DURABLE BOOLEAN NOT NULL ,
|
||||
IS_NONCONCURRENT BOOLEAN NOT NULL ,
|
||||
IS_UPDATE_DATA BOOLEAN NOT NULL ,
|
||||
REQUESTS_RECOVERY BOOLEAN NOT NULL ,
|
||||
JOB_DATA IMAGE NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||
REPEAT_COUNT BIGINT NOT NULL ,
|
||||
REPEAT_INTERVAL BIGINT NOT NULL ,
|
||||
TIMES_TRIGGERED BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_SIMPROP_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||
STR_PROP_1 VARCHAR(512) NULL,
|
||||
STR_PROP_2 VARCHAR(512) NULL,
|
||||
STR_PROP_3 VARCHAR(512) NULL,
|
||||
INT_PROP_1 INTEGER NULL,
|
||||
INT_PROP_2 INTEGER NULL,
|
||||
LONG_PROP_1 BIGINT NULL,
|
||||
LONG_PROP_2 BIGINT NULL,
|
||||
DEC_PROP_1 NUMERIC(13,4) NULL,
|
||||
DEC_PROP_2 NUMERIC(13,4) NULL,
|
||||
BOOL_PROP_1 BOOLEAN NULL,
|
||||
BOOL_PROP_2 BOOLEAN NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_BLOB_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||
BLOB_DATA IMAGE NULL
|
||||
);
|
||||
|
||||
CREATE TABLE QRTZ_TRIGGERS (
|
||||
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||
JOB_NAME VARCHAR (200) NOT NULL ,
|
||||
JOB_GROUP VARCHAR (200) NOT NULL ,
|
||||
DESCRIPTION VARCHAR (250) NULL ,
|
||||
NEXT_FIRE_TIME BIGINT NULL ,
|
||||
PREV_FIRE_TIME BIGINT NULL ,
|
||||
PRIORITY INTEGER NULL ,
|
||||
TRIGGER_STATE VARCHAR (16) NOT NULL ,
|
||||
TRIGGER_TYPE VARCHAR (8) NOT NULL ,
|
||||
START_TIME BIGINT NOT NULL ,
|
||||
END_TIME BIGINT NULL ,
|
||||
CALENDAR_NAME VARCHAR (200) NULL ,
|
||||
MISFIRE_INSTR SMALLINT NULL ,
|
||||
JOB_DATA IMAGE NULL
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_CALENDARS ADD
|
||||
CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
CALENDAR_NAME
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_CRON_TRIGGERS ADD
|
||||
CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_FIRED_TRIGGERS ADD
|
||||
CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
ENTRY_ID
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD
|
||||
CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_SCHEDULER_STATE ADD
|
||||
CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
INSTANCE_NAME
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_LOCKS ADD
|
||||
CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
LOCK_NAME
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_JOB_DETAILS ADD
|
||||
CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
JOB_NAME,
|
||||
JOB_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD
|
||||
CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD
|
||||
CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_TRIGGERS ADD
|
||||
CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
);
|
||||
|
||||
ALTER TABLE QRTZ_CRON_TRIGGERS ADD
|
||||
CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) REFERENCES QRTZ_TRIGGERS (
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) ON DELETE CASCADE;
|
||||
|
||||
|
||||
ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD
|
||||
CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) REFERENCES QRTZ_TRIGGERS (
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD
|
||||
CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) REFERENCES QRTZ_TRIGGERS (
|
||||
SCHED_NAME,
|
||||
TRIGGER_NAME,
|
||||
TRIGGER_GROUP
|
||||
) ON DELETE CASCADE;
|
||||
|
||||
|
||||
ALTER TABLE QRTZ_TRIGGERS ADD
|
||||
CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY
|
||||
(
|
||||
SCHED_NAME,
|
||||
JOB_NAME,
|
||||
JOB_GROUP
|
||||
) REFERENCES QRTZ_JOB_DETAILS (
|
||||
SCHED_NAME,
|
||||
JOB_NAME,
|
||||
JOB_GROUP
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
40
batch-quartz/src/main/resources/quartz.yml
Normal file
40
batch-quartz/src/main/resources/quartz.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
spring:
|
||||
quartz:
|
||||
scheduler:
|
||||
instanceName: batch-quartz
|
||||
instance-id: SYS_PROP
|
||||
name: BatchQuartzScheduler
|
||||
|
||||
org:
|
||||
quartz:
|
||||
jobStore:
|
||||
tablePrefix: QRTZ_
|
||||
isClustered: true
|
||||
misfireThreshold: 2000
|
||||
clusterCheckinInterval: 1000
|
||||
class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
|
||||
acquireTriggersWithinLock: true
|
||||
scheduler:
|
||||
instance-id:
|
||||
instanceName:
|
||||
rmi:
|
||||
export: false
|
||||
proxy: false
|
||||
batchTriggerAcquisitionMaxCount: 20
|
||||
idleWaitTime: 1000
|
||||
skipUpdateCheck: true
|
||||
threadPool:
|
||||
class: org.quartz.simpl.SimpleThreadPool
|
||||
threadCount: 10
|
||||
threadPriority: 5
|
||||
threadsInheritContextClassLoaderOfInitializingThread: true
|
||||
threadNamePrefix: BatchQuartz
|
||||
# dataSource:
|
||||
# nxcus:
|
||||
# driver: org.h2.Driver #oracle.jdbc.driver.OracleDriver
|
||||
# URL: 'jdbc:h2:mem:test' #jdbc:oracle:thin:@polarbear:1521:dev
|
||||
# user: mindol1004
|
||||
# password: 1111
|
||||
# maxConnections: 5
|
||||
# validationQuery: select 0 from dual
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.spring.batch_quartz;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class BatchQuartzApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user