本文出处:http://blog.csdn.net/chaijunkun/article/details/7673931,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。
Sping和Hibernate在去年年底都发布了新的版本,现在我做的项目都将最新版本的Spring和Hibernate引入了,使用效果良好。不过最近遇到了一个以前没有遇到的问题——实体的延时加载。
对于关系型数据库,表与表之间的某些字段是通过一对多、多对一或者是多对多的关系来维护的,因此Hibernate引入了延迟加载的优化方法。例如一个雇员,包含姓名,性别等等信息,而最重要的就是所属部门。这些员工与部门就存在着多对一的关系。当我从数据库中获取到雇员的时候,假如没有延迟加载优化,那么雇员的信息以及部门相关的属性都会一并加载下来,假如一个部门信息内再有关联的其它信息,那就会占用很多时间来查询。然而有时候,我们获取雇员仅仅是为了显示一下姓名。
上面是针对延迟加载应用场景的一个表述,下面是我的代码:
首先是部门表的关联代码
package blog.csdn.net.chaijunkun.pojo;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class Department implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3760808870590915399L;
@Id
@GeneratedValue
private Long departId;
@Column(nullable=false)
private String departName;
@Column(nullable=false)
private String departLocate;
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="department")
private Set<Employee> employees = new HashSet<Employee>(0);
public Long getDepartId() {
return departId;
}
public void setDepartId(Long departId) {
this.departId = departId;
}
public String getDepartName() {
return departName;
}
public void setDepartName(String departName) {
this.departName = departName;
}
public String getDepartLocate() {
return departLocate;
}
public void setDepartLocate(String departLocate) {
this.departLocate = departLocate;
}
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
}
接下来是雇员的关联代码
package blog.csdn.net.chaijunkun.pojo;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@Entity
public class Employee implements Serializable {
/**
*
*/
private static final long serialVersionUID = 7226562979319568974L;
@Id
@GeneratedValue
private Long empId;
@Column(nullable=false)
private String fullName;
@Column(nullable=false)
private Boolean sex;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="departId")
private Department department;
public Long getEmpId() {
return empId;
}
public void setEmpId(Long empId) {
this.empId = empId;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
这里,雇员表的部门列我使用了延迟加载配置。其它配置都是JPA中普通的注解配置。列名称与属性名称相同时就不用配置@Column注解的name属性。
然后就按部就班地写了表操作的服务及实现。这里就不多说了。
接下来在Spring MVC中标注了@Controller的类的方法中尝试按照雇员id来获取雇员信息:
@Controller
@RequestMapping(value="/show.do")
public class TestController {
@Resource
private EmployeeService employeeService;
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping
public ModelAndView getEmployee(@RequestParam(required= true) Long empId, Map model){
Employee employee= employeeService.find(empId);
if (employee!=null){
System.out.println(employee.getFullName());
//下面代码出问题了
System.out.println(employee.getDepartment().getDepartName());
model.put("employee", employee);
return new ModelAndView("show", model);
}else{
return null;
}
}
}
当我尝试访问http://localhost/show.do?empId=1的时候发现出现了如下的错误:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session at
很明显是由于jpa的entityManager将事务关闭了,因此延迟加载时找不到存在的会话来运行接下来的自动查询。
在网上找了很多资料,最终找到了解决办法:
首先在配置JPA的EntityManager配置文件中加入如下配置:
<!-- 建立视图内拦截器来解决JPA中访问延迟加载属性时产生的无会话异常 -->
<!-- LazyInitializationException: could not initialize proxy no session -->
<!-- 此拦截器会注入到servlet配置中的DefaultAnnotationHandlerMapping中 -->
<bean name="openEntityManagerInViewInterceptor"
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor">
<property name="entityManagerFactory">
<ref bean="entityManagerFactory" />
</property>
</bean>
然后在配置Servlet的配置文件中更改支持@RequestMapping注解的配置:
原来的多数配置都是这样的:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
现在我们为这个默认的注解处理映射加入视图内拦截器来自动生成会话:
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openEntityManagerInViewInterceptor" />
</list>
</property>
</bean>
好了,加入了以上配置后,再访问同样的接口,发现问题解决了。如果你在使用JPA的时候打开了show_sql选项,你会看到执行了两条JPQL语句。