案例描写
本文主要描写了开拓中常见的几个与spring懒加载和事务相关的案例,主要描写常见的利用场景,以及如何规避他们,给出详细的代码。
1. 在新的线程中,会见某个耐久化工具的懒加载属性。
2. 在quartz按时任务中,会见某个耐久化工具的懒加载属性。
3. 在dubbo,motan一类rpc框架中,长途挪用时处事端session封锁的问题。
上面三个案例,其实焦点都是一个问题,就是牵扯到spring对事务的打点,而懒加载这个技能,只是较量容易浮现失事务堕落的一个实践,主要用它来激发问题,进而对问题举办思考。
前期筹备
为了能直观的袒暴露第一个案例的问题,我新建了一个项目,回收传统的mvc分层,一个student.Java实体类,一个studentDao.java耐久层,一个studentService.java业务层,一个studentController节制层。
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
getter..setter..
}
耐久层利用springdata,图纸加密,软件开发,框架自动扩展出CURD要领
public interface StudentDao extends JpaRepository<Student, Integer>{
}
service层,先给出普通的挪用要领。用于错误演示。
@Service
public class StudentService {
@Autowired
StudentDao studentDao;
public void testNormalGetOne(){
Student student = studentDao.getOne(1);
System.out.println(student.getName());
}
}
留意:getOne和findOne都是springdata提供的按照id查找单个实体的要领,区别是前者是懒加载,后者是当即加载。我们利用getOne来举办懒加载的尝试,就不消大费周章去写懒加载属性,配置多个实体类了。
controller层,不是简简朴单的挪用,而是在新的线程中挪用。利用controller层来取代单位测试(实际项目中,凡是利用controller挪用service,然后在欣赏器可能http东西中挪用触发,较为利便)
@RequestMapping("/testNormalGetOne")
@ResponseBody
public String testNormalGetOne() {
new Thread(new Runnable() {
@Override
public void run() {
studentService.testNormalGetOne();
}
}).start();
return "testNormalGetOne";
}
启动项目后,会见localhost:8080/testNormalGetOne报错如下:
Exception in thread "Thread-6" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:148)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at com.example.transaction.entity.Student_$$_jvste17_0.getName(Student_$$_jvste17_0.java)
at com.example.transaction.service.StudentService.testNormalGetOne(StudentService.java:71)
at com.example.transaction.service.StudentService$$FastClassBySpringCGLIB$$f8048714.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)
at com.example.transaction.service.StudentService$$EnhancerBySpringCGLIB$$a6640151.testNormalGetOne(<generated>)
at com.example.transaction.controller.StudentController$1.run(StudentController.java:71)
at java.lang.Thread.run(Thread.java:745)
问题阐明
no session说明白什么?
原理很简朴,因为spring的session是和线程绑定的,在整个model->dao->service->controller的挪用链中,这种事务和线程绑定的机制很是契合。而我们呈现的问题正式由于新开启了一个线程,软件开发,这个线程与挪用链的线程不是同一个。
问题办理
我们先利用一种不太优雅的方法办理这个问题。在新的线程中,手动打开session。
public void testNormalGetOne() {
EntityManagerFactory entityManagerFactory = ApplicationContextProvider.getApplicationContext().getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder entityManagerHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, entityManagerHolder);
Student student = studentDao.getOne(1);
System.out.println(student.getName());
TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(entityManager);
}