이클립스 스프링 배치 프로젝트 생성 및 스케줄링 실행 방법

이클립스 STS 사용법

STS는 이클립스에서 스프링 부트 프로젝트를 쉽게 관리하고 실행할 수 있게 도와주는 플러그인입니다.
독립형 STS 이클립스를 설치해도 되고, 기존 이클립스에 STS 플러그인을 설치해도 됩니다.

독립형 STS 이클립스 설치 방법

https://spring.io/tools
Spring Tools for Eclipse - WINDOWS X86_64 선택하여 다운로드 후 압축 해제합니다.
SpringToolSuite4.exe 파일을 실행하면 STS 이클립스가 실행됩니다.
실행 시 프로젝트 workspace를 지정해야 합니다.

기존 Spring Batch 프로젝트 import 방법

프로젝트 workspace로 기존 Spring Batch 프로젝트 이동 > File > Import > Maven > Existing Maven Projects (Gradle 프로젝트는 Gradle > Existing Gradle Project) > Root Directory : Browse… > 프로젝트 폴더 선택 > Finish


이클립스 스프링배치 사용법

스프링배치 프로젝트 생성 방법

이클립스 > File > Other… > Spring Boot > Spring Stater Project >

Service URL https://start.spring.io (그대로 두기)
Name 프로젝트명-batch
Type Maven, Gradle 중 원하는 Build Tool 선택
Packaging 스프링 부트 내장 톰캣으로 실행 예정 : jar 선택
또는
외장 톰캣에 올릴 경우 : war 선택
Java Versiion JDK 버전 선택
Group 도메인에서 www 제외하고 거꾸로 작성

예시 : www.geniatutor.co.kr = kr.co.geniatutor
Artifact 보통 프로젝트 이름과 동일하게 설정
Description 프로젝트 설명 작성
Package Group ID 기반으로 프로젝트별 하위 패키지 추가하여 구분

예시 : 배치 프로젝트 = kr.co.geniatutor.batch

위 참고하여 입력 > Next > Dependencies 창에서 I/O : Spring Batch 선택 > Finish


스프링배치 프로젝트 DB 사용법

DB 정보 설정

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://DB서버IP:3306/DB명
spring.datasource.username=유저명
spring.datasource.password=패스워드

mybatis.mapper-locations=classpath:mapper/**/*.xml

application.properties 파일에 DB 정보를 작성하고, Mapper 위치를 설정합니다.

개발/운영 환경별 프로파일 설정

개발 설정 파일 application.properties 또는 application-dev.properties
운영 설정 파일 application-prod.properties

JDBC 드라이버 의존성 추가

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

Maven을 사용하는 경우, pom.xml dependencies 안에 위와 같이 추가합니다.
프로젝트에서 연결할 DB에 맞는 의존성을 추가해야 합니다.
spring-boot-starter에서 의존성 버전을 관리하는 BOM을 사용하므로, 버전을 명시하지 않아도 됩니다.

MyBatis 의존성 추가

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.2.0</version>
</dependency>

MyBatis 의존성을 추가해야 Mapper.java에 @Mapper 어노테이션을 달 수 있습니다.

메타데이터 테이블 사용 설정

spring.batch.jdbc.initialize-schema=always

application.properties 파일에 스프링배치 메타데이터 테이블 사용 설정을 합니다.
개발 단계에서는 항상 새 테이블 생성하는 always로 테스트 합니다.
운영에서는 초기 1회만 always로 실행하여 테이블을 만들고, 테이블을 생성하지 않는 never로 고정하여 사용합니다.

메타데이터 테이블 미생성 시 실행 에러메시지

java.lang.IllegalStateException: Failed to execute ApplicationRunner
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:785) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:772) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:345) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.4.jar:2.5.4]
	at kr.co.geniatutor.batch.GeniaBatchApplication.main(GeniaBatchApplication.java:14) ~[classes/:na]
Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]; nested exception is java.sql.SQLSyntaxErrorException: Table 'genia_db.batch_job_instance' doesn't exist
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:239) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1541) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:713) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:744) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:757) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:815) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.batch.core.repository.dao.JdbcJobInstanceDao.getJobInstance(JdbcJobInstanceDao.java:151) ~[spring-batch-core-4.3.3.jar:4.3.3]
	at org.springframework.batch.core.repository.support.SimpleJobRepository.isJobInstanceExists(SimpleJobRepository.java:93) ~[spring-batch-core-4.3.3.jar:4.3.3]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.9.jar:5.3.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.9.jar:5.3.9]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.9.jar:5.3.9]
	at jdk.proxy2/jdk.proxy2.$Proxy46.isJobInstanceExists(Unknown Source) ~[na:na]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:128) ~[spring-batch-core-4.3.3.jar:4.3.3]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.9.jar:5.3.9]
	at jdk.proxy2/jdk.proxy2.$Proxy46.isJobInstanceExists(Unknown Source) ~[na:na]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.getNextJobParameters(JobLauncherApplicationRunner.java:206) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.execute(JobLauncherApplicationRunner.java:198) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.executeLocalJobs(JobLauncherApplicationRunner.java:173) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.launchJobFromProperties(JobLauncherApplicationRunner.java:160) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:155) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:150) ~[spring-boot-autoconfigure-2.5.4.jar:2.5.4]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:782) ~[spring-boot-2.5.4.jar:2.5.4]
	... 5 common frames omitted
Caused by: java.sql.SQLSyntaxErrorException: Table 'genia_db.batch_job_instance' doesn't exist
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-java-8.0.26.jar:8.0.26]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.26.jar:8.0.26]
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.26.jar:8.0.26]
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1003) ~[mysql-connector-java-8.0.26.jar:8.0.26]
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) ~[HikariCP-4.0.3.jar:na]
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]
	at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:722) ~[spring-jdbc-5.3.9.jar:5.3.9]
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-5.3.9.jar:5.3.9]
	... 38 common frames omitted

배치 및 DB 로그 출력

logging.level.org.springframework.batch=DEBUG
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

application.properties에 배치 및 SQL 로그를 출력하도록 설정합니다.


스프링배치 프로젝트 설정 방법

내장 톰캣 실행 포트 설정

src > main > resources > application.properties 파일에 서버 포트를 추가합니다.

server.port=8081

server.port를 설정하지 않으면, 기본 포트는 8080입니다.
실행 시 다른 톰캣 서비스와 충돌하지 않도록 포트를 변경해주는 것이 좋습니다.

프로젝트 JDK 변경

스프링 배치 프로젝트 우클릭 > Properties > Java Build Path > Libraries > Modulepath > JRE System Library 선택 > Edit… > Workspace default JRE 선택 > Finish > Apply and Close

프로젝트 내 pom.xml 또는 build.gradle에서도 java.version을 변경해줘야 합니다.

pom.xml 수정 반영 방법
프로젝트 우클릭 > Maven > Update Project

Java 버전 11로 다운그레이드 시

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.5.4</version>
  <relativePath/>
</parent>

pom.xml에서 Spring Boot 버전을 Java 11과 호환되는 2.5.x 대로 변경해야 합니다.


스프링배치 실행 및 빌드 방법

독립형 STS 이클립스에서 스프링배치 실행 방법

Boot Dashboard 탭 > local > 스프링배치 프로젝트 선택 > Start or Restart 버튼 클릭
스프링 배치는 정의된 모든 배치 스텝 처리 후 JobExecution 상태가 완료로 변경되면 자동 종료됩니다.
cron 등으로 주기적 실행을 설정한 경우는 애플리케이션이 종료되지 않고 계속 실행됩니다.

프로파일 변경 실행
Boot Dashboard 탭 > local > 스프링배치 프로젝트 우클릭 > Open Config > Profile : 변경할 프로파일 선택 > Apply > Debug

기존 이클립스에서 스프링배치 실행 방법

스프링배치 프로젝트 우클릭 > Run As > Spring Boot App

스프링배치 jar 빌드 방법

Run > Run Congigurations… > Maven Build 우클릭 > New configuration > Name : 프로젝트명 입력 > workspace… : 빌드할 프로젝트 선택 > Goals : clean package 입력 > JRE 탭 : Alternate JRE 선택 > Apply > Run
이후 재빌드 시에는 Run > Run Congigurations… > Maven Build 목록에서 프로젝트명 더블클릭 하면 됩니다.

서버 구분별 Goals 예시

개발서버용 Goals clean package -D spring.profiles.active=dev
운영서버용 Goals clean package -D spring.profiles.active=prod -D skipTests

빌드 시 프로파일에 맞는 application.properties 로드 후 TEST 코드 실행이 성공하면 패키징이 진행됩니다.
-D skipTests 옵션을 추가하면 DB 연결 및 테스트 코드 실행 없이 바로 패키징 됩니다.

리눅스 스프링배치 jar 실행 명령어

# 개발 서버 실행
java -jar 프로젝트명.jar
또는
java -jar -Dspring.profiles.active=dev 프로젝트명.jar

# 운영 서버 실행
java -jar -Dspring.profiles.active=prod 프로젝트명.jar

프로파일 옵션 미설정 시, 실행된 프로젝트에 application.properties가 기본 적용됩니다.
프로파일 옵션 설정 시, application-프로파일명.properties가 적용됩니다.


스프링배치 서비스 등록 방법

리눅스 서버 재기동 시 스프링배치 자동 실행 가능하도록, 시스템 서비스 등록하는 방법입니다.

서비스 파일 생성

sudo vi /etc/systemd/system/프로젝트명-batch.service

아래와 같이 내용을 작성합니다.

[Unit]
Description=Spring Batch Job
After=network.target

[Service]
User=유저명
WorkingDirectory=/opt/프로젝트명-batch
ExecStart=/java경로 -jar -Dspring.profiles.active=프로파일명 /opt/프로젝트명-batch/프로젝트명.jar
SuccessExitStatus=143
Restart=always

[Install]
WantedBy=multi-user.target

파일질라로 리눅스 /opt/프로젝트명-batch 폴더에 스프링배치 jar 파일을 옮긴 뒤, 서비스 파일을 생성하면 됩니다.

리눅스 java 경로 확인 명령어

which java

리눅스 java 버전 확인 명령어

java -version

스프링 배치 프로젝트 java 버전과 동일해야 합니다.

서비스 등록 및 자동실행 설정

# 서비스 등록 및 수정
sudo systemctl daemon-reload

# 서비스 시작
sudo systemctl start 프로젝트명-batch

# 서버 재부팅 시 자동 시작 설정
sudo systemctl enable 프로젝트명-batch

# 서버 재부팅 시 자동 시작 여부 확인
sudo systemctl is-enabled 프로젝트명-batch

스프링배치 실행 로그 확인

# 특정 시간 이후 로그 확인
journalctl -u genia-batch.service --since "2025-04-10 00:00"

# 금일 로그 확인
journalctl -u 프로젝트명-batch.service --since today

# 전체 로그 확인
journalctl -u 프로젝트명-batch.service

전체 로그 확인 시 너무 많은 로그가 나오므로, 시간 조건을 넣어서 확인하는 것이 좋습니다.


스프링배치 실행 시 에러 수정

java 파일 실행 권한이 없는 경우

Apr 10 09:28:40 genius-staging systemd[1]: Started Genia Spring Batch Job.
Apr 10 09:28:40 genius-staging systemd[1]: genia-batch.service: Main process exited, code=exited, status=203/EXEC
Apr 10 09:28:40 genius-staging systemd[1]: genia-batch.service: Failed with result 'exit-code'.
Apr 10 09:28:40 genius-staging systemd[1]: genia-batch.service: Service RestartSec=100ms expired, scheduling restart.
Apr 10 09:28:40 genius-staging systemd[1]: genia-batch.service: Scheduled restart job, restart counter is at 1.
Apr 10 09:28:40 genius-staging systemd[1]: Stopped Genia Spring Batch Job.
Apr 10 09:28:40 genius-staging systemd[1]: Started Genia Spring Batch Job.

스프링 배치 실행 시 실패하고, 위와 같은 에러 로그가 확인되는 경우입니다.

java 파일 실행 권한 확인

cd java경로
ll

java 파일 경로에서 ll으로 파일 권한 상태 확인 시 아래와 같이 x(실행 권한)이 없는 것을 확인할 수 있습니다.

-rw-r--r-- 1 root root  12848 Apr 10 09:25 java

java 파일 실행 권한 생성 명령어

chmod +x /usr/local/jdk-11/bin/*

권한 생성 후 스프링부트를 재실행 하면 됩니다.

DB 인증 방식 에러

Apr 10 09:44:03 genius-staging java[802301]: java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:828) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:448) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:44:03 genius-staging java[802301]:         at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]

MySQL 8 이상 기본 인증 방식은 공개 키를 통해 비밀번호를 암호화해서 전송하는 caching_sha2_password입니다.
해당 방식에서 클라이언트가 서버의 공개 키를 받아들이는 설정이 안 돼 있으면 위와 같은 오류가 나옵니다.
만약, DB 사용자 비밀번호 인증 방식이 mysql_native_password라면 이 문제는 발생하지 않습니다.

DB 인증 방식 에러 해결 방법

allowPublicKeyRetrieval=true&useSSL=true

스프링배치 application.properties 파일 JDBC URL 뒤에 위 옵션을 추가 후 재빌드하고 실행하면 됩니다.
allowPublicKeyRetrieval=true는 MySQL 클라이언트(JDBC 드라이버)가 비밀번호를 암호화해서 전송할 수 있도록, 서버에 공개키 요청하는 것을 허용하는 옵션입니다.
useSSL=true는 서버에서 SSL 인증서를 사용하는 경우, 비밀번호를 평문으로 전송하지 않도록 보호하는 옵션입니다.

DB 연결 거부 에러

Apr 10 09:59:30 genius-staging java[803094]: java.sql.SQLException: Access denied for user 'DB유저명'@'172.16.0.8' (using password: YES)
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:828) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:448) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]
Apr 10 09:59:30 genius-staging java[803094]:         at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) ~[mysql-connector-java-8.0.26.jar!/:8.0.26]

현재 서버 IP로 접속할 수 있는 유저 권한이 없어서 나는 스프링배치 실행 시 에러입니다.

DB 유저 권한 확인 쿼리

-- 현재 유저로 연결 가능한 host 목록 확인
SELECT host, user FROM mysql.user WHERE user = 'DB유저명';

-- 전체 유저 권한 목록 확인
SELECT user, host, plugin FROM mysql.user;

host가 %로 되어 있으면, 모든 IP에서 접근할 수 있는 계정이라는 뜻입니다.
현재 유저로 접근 가능한 IP가 없으면, GRANT 쿼리로 권한을 신규 생성하거나 다른 유저로 연결해야 합니다.

DB 유저 권한 생성 쿼리

GRANT ALL PRIVILEGES ON DB명.* TO 'DB유저명'@'172.16.0.8';
FLUSH PRIVILEGES;

권한 생성 불가 오류 메시지

You are not allowed to create a user with GRANT

위와 같은 오류가 나오면, 현재 DB에 연결된 계정에서 GRANT 쿼리 권한이 없는 것입니다.
root 계정 등으로 다시 시도해야 합니다.


스프링배치 개발 예시

매일 0시, 노출 기한이 지난 배너의 노출 여부를 비노출로 변경하는 스프링배치 예시입니다.

배너 비노출 쿼리 작성

src/main/resources/mapper/banner/BannerMapper.xml 파일을 작성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.geniatutor.batch.banner.mapper.BannerMapper">
  <update id="disableExpiredBanners">
    UPDATE
      배너테이블명
    SET
      SHOW_YN = 'N'
    WHERE
      SHOW_YN = 'Y'
    AND
      DEL_YN = 'N'
    AND	
      SHOW_END_DT <![CDATA[<]]> NOW()
  </update>
</mapper>

배너 노출 기한이 지난 배너의 노출여부를 비노출로 변경하는 쿼리 예시입니다.
XML 파일 내에서 부등호를 사용하기 위해 CDATA를 사용하였습니다.

쿼리 Mapper 생성

src/main/java/kr/co/geniatutor/batch/banner/mapper/BannerMapper.java 파일을 작성합니다.

package kr.co.geniatutor.batch.banner.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BannerMapper {
    void disableExpiredBanners();
}

Job 작성

src/main/java/kr/co/geniatutor/batch/banner/job/BannerBatchJobConfig.java 파일을 작성합니다.

package kr.co.geniatutor.batch.banner.job;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import kr.co.geniatutor.batch.banner.mapper.BannerMapper;

@Configuration
public class BannerBatchJobConfig {

  @Autowired
  private JobBuilderFactory jobBuilderFactory;

  @Autowired
  private StepBuilderFactory stepBuilderFactory;

  @Autowired
  private BannerMapper bannerMapper;

  @Bean
  public Job bannerJob() {
    return jobBuilderFactory.get("bannerJob")
            .incrementer(new RunIdIncrementer())
            .start(bannerStep())
            .build();
  }

  @Bean
  public Step bannerStep() {
    return stepBuilderFactory.get("bannerStep")
            .tasklet((contribution, chunkContext) -> {
              bannerMapper.disableExpiredBanners();
              return RepeatStatus.FINISHED;
            })
            .build();
  }
}

스케줄러 작성

src/main/java/kr/co/geniatutor/batch/SchedulerConfig.java 파일을 작성합니다.

package kr.co.geniatutor.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@Configuration
@EnableScheduling
public class SchedulerConfig {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job bannerJob;

    @Scheduled(cron = "0 0 0 * * *")
    public void runBannerJob() throws Exception {
    	// 스프링 배치는 기본적으로 동시에 같은 Job이 중복 실행되지 않도록 방지하므로,
    	JobParameters jobParameters = new JobParametersBuilder()
    	        .addLong("time", System.currentTimeMillis()) // 매번 다른 파라미터 추가
    	        .toJobParameters();
    	
        jobLauncher.run(bannerJob, jobParameters);
    }
}

@EnableScheduling 어노테이션을 단 class는 SpringApplication이 실행될 때 빈으로 등록되고,
@Scheduled가 붙은 메서드를 찾아서 스케줄러에 등록합니다.

cron 스케줄을 0 0 0 * * * 로 설정하면 매일 자정 0시 Job이 실행됩니다.

스프링 배치 활성화

src/main/java/kr/co/geniatutor/GeniaBatchApplication.java 파일에 @EnableBatchProcessing를 추가합니다.

package kr.co.geniatutor.batch;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class GeniaBatchApplication {

	public static void main(String[] args) {
		SpringApplication.run(GeniaBatchApplication.class, args);
	}

}

스프링 배치 미활성화 시 실행 에러메시지

***************************
APPLICATION FAILED TO START
***************************

Description:

Field jobLauncher in kr.co.geniatutor.batch.SchedulerConfig required a bean of type 'org.springframework.batch.core.launch.JobLauncher' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'org.springframework.batch.core.launch.JobLauncher' in your configuration.