相信大家在web开发过程中一定遇到过一种情况,Class班级类一对多关联一个Student学生类,所以为了性能考虑,配置一个lazy-init=true,然后在前台页面需要访问懒加载数据时需要额外配置一个OpenSessionInViewFilter,但是可能并不知道这个过滤器到底做了什么,遇到Quartz定时中访问懒加载问题配置这个filter可没有用了,下面来讨论下,怎么解决这个问题。
@Entity
@Table(name = "clazz")
public class Clazz {
@Id
@GenericGenerator(name = "PKUUID", strategy = "uuid2")
@GeneratedValue(generator = "PKUUID")
@Column(length = 36)
private String id;
private String clazzName;
private String clazzNumber;
@OneToMany(fetch = FetchType.LAZY,mappedBy = "clazz",cascade = CascadeType.ALL)
private List students;
get/set...
}
Student类省略,我们现在数据手动插入一条记录,待会我们在定时器里面查询他。
定时任务代码如下:
public class DemoJob {
@Autowired
ClazzDao clazzDao;
public static final Logger logger = LoggerFactory.getLogger(DemoJob.class);
public DemoJob() {
System.out.println("DemoJob=====>init");
}
public void run() {
Clazz clazz = clazzDao.findOne("ba9071fb-f2da-481a-9a90-371022cda195");
System.out.println(clazz.getClazzName());
System.out.println(clazz.getClazzNumber());
System.out.println(clazz.getStudents());
logger.info("run============================@" + new Date());
}
}
我们先不做额外配置看看运行结果
计算机
131
2016-11-04 10:16:50.213 ERROR 6692 --- [ryBean_Worker-1] org.quartz.core.JobRunShell : Job job_work.job_name threw an unhandled Exception:
org.springframework.scheduling.quartz.JobMethodInvocationFailedException: Invocation of method 'run' on target class [class com.example.quartz.job.DemoJob] failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.quartz.common.entity.Clazz.students, could not initialize proxy - no Session
可以看到,非懒加载数据可以顺利查出来,但是懒加载数据会报no session
很明显,session已经关闭了。
解决方案1
关闭懒加载,好吧,当我没讲
解决方案2
给定时器调度的方案用事务包裹
原理就是,用事务包裹之后,会当做一个整体,没有提交时session不会交给SessionFactory管理
解决方案3
既然session关闭了,那就打开它就行了,类似于OpensessionInView,不过要取决于你的持久化方案来决定到底打开什么,一般是SessionFactory或者EntityManager,由于我是采用的JPA,所以使用后者
配置一个任务监听器,用于打开session
public class OpenEntityManagerJobListener extends JobListenerSupport implements ApplicationContextAware {
@Override
public String getName() {
return "OpenEntityManagerJobListener";
}
EntityManagerFactory entityManagerFactory;
@Override
public void jobToBeExecuted(JobExecutionContext context) {
entityManagerFactory = applicationContext.getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, emHolder);
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if(this.applicationContext ==null) throw new RuntimeException("applicationContext is null");
}
}
之后记得给调度器注册全局任务监听器,对所有任务监听
//调度工厂
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setTriggers(triggerFactoryBeans().getObject());
factoryBean.setGlobalJobListeners(openEntityManagerJobListener());
return factoryBean;
}
再来试试运行一下定时任务
2016-11-04 10:29:12.234 INFO 11188 --- [ main] com.example.quartz.CommonApp : Started CommonApp in 12.067 seconds (JVM running for 12.675)
计算机
131
Hibernate: select students0_.clazz_id as clazz_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.clazz_id as clazz_id4_1_1_, students0_.stu_name as stu_name2_1_1_, students0_.stu_numer as stu_numer3_1_1_ from student students0_ where students0_.clazz_id=?
[com.example.quartz.common.entity.Student@2cd010f8, com.example.quartz.common.entity.Student@100a20c4]
2016-11-04 10:29:12.336 INFO 11188 --- [ryBean_Worker-1] com.example.quartz.job.DemoJob : run============================@Date