您的位置:首页 > 编程语言 > Java开发

基于注解的Spring MVC与JPA如何解决实体的延时加载问题

2012-06-18 19:05 567 查看
本文出处: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语句。

2014年11月14日补充:今天发现按照上述配置后仍然可能在懒加载时无法获取关联对象。经过检查,是由于没有加入事务造成的。首先要在spring配置文件中加入事务注解支持选项:

<tx:annotation-driven />
该选项默认指定的事务管理器是bean id为transactionManager的对象,完整配置为:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
接下来最重要的是要在操作数据的service层增加@Transactional注解(javax.transaction.Transactional),例如:
@Transactional
public String getRoleName(Long id){
	User u = userRepository.findOne(id);
	return u.getRole().getName();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐