案例描写
本文主要描写了开拓中常见的几个与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); }