JobPersistenceException 에러: JobDataMap 직렬화 실패
RECOMMEND POSTS BEFORE THIS
1. 문제 상황
쿼츠 클러스터링(Quartz Clustering)을 구현하면서 다음과 같은 에러를 만났다.
Caused by: java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'blogService' is not serializable: blog.in.action.service.impl.BlogServiceImpl
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeJobData(StdJDBCDelegate.java:3083)
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.updateJobDetail(StdJDBCDelegate.java:647)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1115)
...
에러 로그를 보면 BlogServiceImpl 클래스 정보를 데이터베이스에 저장하기 위한 직렬화 작업이 실패한 것으로 보인다. 문제가 발생하는 BlogServiceImpl 클래스가 Serializable 인터페이스를 구현하도록 변경해도 MyBatis의 SqlSessionTemplate 클래스를 직렬화할 수 없다는 에러가 발생한다. 결국 같은 문제에 직면하게 된다.
Caused by: java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'blogService' is not serializable: org.mybatis.spring.SqlSessionTemplate
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeJobData(StdJDBCDelegate.java:3083)
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.updateJobDetail(StdJDBCDelegate.java:647)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1115)
...
비즈니스 로직에서 데이터베이스 기능을 사용하기 위해서는 ServiceImpl 빈(bean) 객체 주입이 필요하다. 이 빈 객체를 주입할 때 문제가 발생한다. 상황을 정리해 보면 다음과 같았다.
QuartzJobBean을 상속한 클래스에서@Autowired키워드를 사용한 빈 객체 주입이 안 된다.- 쿼츠 클러스터링 구축 시
JobDataMap파라미터와Setter메서드로 빈 객체를 주입하면 에러가 발생한다.
2. 해결 방법
applicationContext.xml 설정과 Job 클래스를 다음과 같이 수정하면 문제를 해결할 수 있다.
JobDetailFactoryBean생성 시jobDataAsMap속성 관련 설정을 제거한다.SchedulerFactoryBean생성 시applicationContextSchedulerContextKey속성 값을applicationContext키워드로 지정하는 설정을 추가한다.
<bean name="blogJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="blog.in.action.job.BlogJob"/>
<property name="durability" value="true"/>
<!-- 제거 -->
<!-- <property name="jobDataAsMap">
<map>
<entry key="blogService" value-ref="blobService"/>
</map>
</property> -->
</bean>
...
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
</list>
</property>
<!-- 추가 -->
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
...
</bean>
BlogJob 클래스에 다음과 같은 코드 변경이 필요하다.
Setter메서드는 제거한다.executeInternal메서드에 파라미터로 전달받은JobExecutionContext객체에서 스케줄러를 획득한다.- 스케줄러에서
applicationContext키워드로 스프링 애플리케이션 컨텍스트(spring application context) 정보를 획득한다. - Spring 컨텍스트에서
getBean메서드를 통해 원하는 빈(bean)을 꺼내어 사용한다.
package blog.in.action.job;
import blog.in.action.service.BlogService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class BlogJob extends QuartzJobBean {
private BlogService blogService;
// 제거
// public void setBlogService(BlogService blogService) {
// this.blogService = blogService;
// }
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
// 추가
blogService = ((ApplicationContext) jobExecutionContext.getScheduler().getContext().get("applicationContext")).getBean(BlogService.class);
blogService.updateTest();
} catch (Exception e) {
e.printStackTrace();
}
}
}
댓글남기기