(十六)Hibernate中的延迟加载
一、什么是延迟加载
为了节省Hibernate加载对象的性能节销,在Hibernate中真正需要用到这个对象时,才会发出
SQL语句来抓取这个对象。这一个过程称为延迟加载。
二、延迟加载的分类
A:实体对象的延迟加载
B:一对多|多对多的延迟加载
C:多对一|一对一的延迟加载
D:属性的延迟加载
-
A:实体对象的延迟加载:使用session.get()和session.load()获取对象的区别就是是否开启延迟加载。
Hibernate只加载实体对象的ID,需要其他属性,才真正的发出SQL来加载这个对象。
Load:采用延迟加载 加载到的是一个代理对象
Get:没有采用延迟加载 加载到的是一个实体对象。
- 案例:
User user=(User)session.load(clazz, id);//直接返回的是代理对象
System.out.println(user.getId());//没有发送sql语句到数据库加载
user.getName();//创建真实的User实例,并发送sql语句到数据库中
- 注意:1.不能判断User=null;代理对象不可能为空
2.代理对象的限制:和代理关联的session对象,如果session关闭后访问代理则抛异常。session关闭之前访问数据库
-
B:一对多|多对多的延迟加载
fetch = FetchType.Lazy:表示开启延迟加载。读取班级时,不会发出读取学生的SQL语句。等真正使用学生数据时,才会发出一条SQL语句读取学生
fetch = FetchType.EAGER:取消延迟加裁。读取班级会左关联读取学生。
@OneToMany(cascade = { CascadeType.REMOVE },fetch=FetchType.EAGER)
@JoinColumn(name = "classes_id")
@OrderBy(value = " studentID desc")
public List<StudentBean> getStuList() {
return stuList;
}
-
C : 多对一|一对一的延迟加裁
默认是取消延迟加载的。
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "group_id")
private GroupBean groupBean;
- 延迟加载带来的问题: session关闭之后,再访问代理对象(延迟加载获取的是代理对象)会抛出“no session”异常。
package action; import java.util.Set; import javassist.compiler.ast.IntConst; import org.hibernate.Session; import org.hibernate.Transaction; import bean.ClassBean; import bean.StudentBean; import util.HibernateSessionUtil; public class Test { public static void main(String[] args) { ClassBean cla=Test.load(); //当Test.load()执行完毕之后,session就被关闭,这时候再访问代理对象则会抛出异常。 使用session。get就不会出现这个问题 System.out.println(cla.getClassName()); } private static ClassBean load() { ClassBean cla = null; Session session = null; Transaction tran = null; try { session = HibernateSessionUtil.getSession(); tran = session.beginTransaction(); cla = (ClassBean) session.load(ClassBean.class, new Integer(2)); //使用延迟加载,获得的是代理对象 tran.commit(); return cla; } catch (Exception e) { e.printStackTrace(); tran.rollback(); } finally { HibernateSessionUtil.closeSession(); //关闭session } return null; } }
- 橙色字体处代码会出现“no session”异常。
-
解决延迟加载带来的问题:
1. 在后台,把前台要显示的数据准备好。(适用于非WEB程序和WEB程序)
2. 使用延迟加载,又要把Session关掉。而且是前台展现数据的时候,才发给SQL语句。(仅限于WEB程序) 原理:将Session的关闭延迟到页面加载完成之后,才关闭。
3. 在2的的基础上面,将在页面中手工关闭的Session代码,改为自动调用关闭。 过滤器来实现。
1. 使用第一种方法解决延迟加载带来的问题(在后台,把前台要显示的数据准备好)
package action; import java.util.HashMap; import java.util.Map; import java.util.Set; import javassist.compiler.ast.IntConst; import org.hibernate.Session; import org.hibernate.Transaction; import bean.ClassBean; import bean.StudentBean; import util.HibernateSessionUtil; public class Test { public static void main(String[] args) { Map<String, Object> dataMap = Test.load(); // Test.load()方法不再直接返回一个ClassBean对象然后再由这个对象得到StudentBean对象,而是 // Test.load()方法里直接把ClassBean和StudentBean对象直接返回 ClassBean classBean = (ClassBean) dataMap.get("classBean"); Set<StudentBean> stuSet=(Set<StudentBean>)dataMap.get("stuSet"); System.out.println(stuSet.size()); } private static Map<String, Object> load() { Map<String, Object> dataMap = new HashMap<String, Object>(); Session session = null; Transaction tran = null; try { session = HibernateSessionUtil.getSession(); tran = session.beginTransaction(); ClassBean classBean = (ClassBean) session.get(ClassBean.class, new Integer(1)); Set<StudentBean> stuSet = classBean.getStuSet(); dataMap.put("classBean", classBean); stuSet.size(); //这行不能省略,因为classBean.getStuSet();并不会发出sql语句。 dataMap.put("stuSet", stuSet); tran.commit(); } catch (Exception e) { e.printStackTrace(); tran.rollback(); } finally { HibernateSessionUtil.closeSession(); // 关闭session } return dataMap; } }
2.使用第二种方法解决延迟加载带来的问题(是前台展现数据的时候,才发给SQL语句。(仅限于WEB程序))
- index.jsp
<body> <a href="<%=path%>/servlet/session_1">1:解决延迟加载,将Session的关闭延迟到jsp页面中</a> </body>
- SessionServlet .java
package servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.hibernate.Session; import bean.ClassBean; import util.HibernateSessionUtil; public class SessionServlet extends HttpServlet { public SessionServlet() { super(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("html;charset=UTF-8"); Session session = null; ClassBean classBean = null; try { session = HibernateSessionUtil.getSession(); classBean = (ClassBean) session.load(ClassBean.class, new Integer(1)); } catch (Exception e) { e.printStackTrace(); } finally { //这里不能关闭session,在view1,jsp页面关闭seession } request.setAttribute("classBean", classBean); request.getRequestDispatcher("/view1.jsp").forward(request, response); } }
- view1.jsp
<body> <pre> <h2>班级信息:</h2> 班级id:${requestScope.classBean.classId} 班级名称:${requestScope.classBean.className} <h2>学生信息信息:</h2> <c:forEach var="student" items="${requestScope.classBean.stuSet}"> 学生id:${student.stuId} 学生名:${student.stuName} 班级id:${student.classId} </c:forEach> </pre> <% HibernateSessionUtil.closeSession(); //在这里关闭session,确保页面取到所需要的数据后再关闭session。 %> </body>
结果:
3. 案例三(在2的的基础上面,将在页面中手工关闭的Session代码,改为自动调用关闭。 过滤器来实现。)
- index.jsp
<body> <a href="<%=path%>/servlet/session_1">1:在过滤器中统一关闭session</a> </body>
- SessionServlet.java
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("html;charset=UTF-8"); Session session = null; ClassBean classBean = null; try { session = HibernateSessionUtil.getSession(); classBean = (ClassBean) session.load(ClassBean.class, new Integer(1)); } catch (Exception e) { e.printStackTrace(); } finally { //这里不能关闭session,在view1,在过滤器中关闭seession } request.setAttribute("classBean", classBean); request.getRequestDispatcher("/view1.jsp").forward(request, response); }
- HibernateFilter.java
package filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; public class HibernateFilter implements Filter { private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>(); private static org.hibernate.SessionFactory sessionFactory; private static Configuration configuration = new Configuration(); private static ServiceRegistry serviceRegistry; @Override public void init(FilterConfig arg0) throws ServletException { try { configuration.configure(); serviceRegistry = new ServiceRegistryBuilder().applySettings( configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } catch (Exception e) { System.err.println("%%%% Error Creating SessionFactory %%%%"); e.printStackTrace(); } } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { try { System.out.println("hello"); chain.doFilter(req, res); } catch (Exception e) { e.printStackTrace(); } finally { HibernateFilter.closeSession(); // 在过滤器中统一关闭session } } @Override public void destroy() { } public static Session getSession() throws HibernateException { Session session = (Session) threadLocal.get(); if (session == null || !session.isOpen()) { if (sessionFactory == null) { rebuildSessionFactory(); } session = (sessionFactory != null) ? sessionFactory.openSession() : null; threadLocal.set(session); } return session; } public static void rebuildSessionFactory() { try { configuration.configure(); serviceRegistry = new ServiceRegistryBuilder().applySettings( configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } catch (Exception e) { System.err.println("%%%% Error Creating SessionFactory %%%%"); e.printStackTrace(); } } public static void closeSession() throws HibernateException { Session session = (Session) threadLocal.get(); threadLocal.set(null); if (session != null) { session.close(); } } }
- view1.jsp
<body> <pre> <h2>班级信息:</h2> 班级id:${requestScope.classBean.classId} 班级名称:${requestScope.classBean.className} <h2>学生信息信息:</h2> <c:forEach var="student" items="${requestScope.classBean.stuSet}"> 学生id:${student.stuId} 学生名:${student.stuName} 班级id:${student.classId} </c:forEach> </pre> </body>
结果与上例差不多。
总结:一般使用第二种解决方式来解决延迟加载带来的问题。
D. 属性的延迟加载
- 大字段的属性上面(Oracle中的Clob和Blog,SQLServer中的TExt和Image2种字段),String,int属性没有必要延迟加载。
1:设定延迟加载的注解
// Text映射为string类型
@Lob
@Basic(fetch = FetchType.LAZY)
private String content;
// image映射为字节数组。
@Lob
@Basic(fetch = FetchType.LAZY)
private byte[] filedata;
2:要对对象实现类增强机制。
使用Ant来完成。
- build.xml
<?xml version="1.0" encoding="UTF-8"?> <project name="Hibernate_Project_7" default="instrument" basedir="."> <property name="lib.dir" value="./WebRoot/WEB-INF/lib" /> <property name="classes.dir" value="./WebRoot/WEB-INF/classes" /> <path id="lib.class.path"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> </fileset> </path> <target name="one"></target> <target name="two"></target> <target name="instrument"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> <classpath path="${classes.dir}" /> <classpath refid="lib.class.path" /> </taskdef> <instrument verbose="true"> <fileset dir="${classes.dir}/com/bean"> <include name="LobBean.class" /> <!-- 每次修改LobBean的代码后都需要重新运行ant,否则这个ant会失效 --> </fileset> </instrument> </target> </project>
- 案例
-
BlobBEAN.java
package bean; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.Table; @Entity @Table(name = "t_blob") public class BlobBEAN implements Serializable { @Id @Column(name = "blodid") private Integer blobId; private String name; // String类型映射为文本大字段 @Basic(fetch=FetchType.LAZY) @Lob private String content; // 视频/图片等大字段映射为字节数组 @Basic(fetch=FetchType.LAZY) @Lob private byte[] image; public String getName() { return name; } public void setName(String name) { this.name = name; } public BlobBEAN(Integer blobId, String name, String content, byte[] image) { super(); this.blobId = blobId; this.name = name; this.content = content; this.image = image; } public BlobBEAN() { super(); // TODO Auto-generated constructor stub } public Integer getBlobId() { return blobId; } public void setBlobId(Integer blobId) { this.blobId = blobId; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public byte[] getImage() { return image; } public void setImage(byte[] image) { this.image = image; } }
- build.xml
<?xml version="1.0" encoding="UTF-8"?> <project name="hibernate_lazy" default="instrument" basedir="."> <!--default指默认执行的arget --> <property name="lib.dir" value="./WebRoot/WEB-INF/lib" /> <!--设置lib文件夹的路径 --> <property name="classes.dir" value="./WebRoot/WEB-INF/classes" /> <!--设置classes文件夹的路径 --> <path id="lib.class.path"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> <!-- 引用${lib.dir}路径中所有的jar包--> </fileset> </path> <target name="instrument"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> <classpath path="${classes.dir}" /> <classpath refid="lib.class.path" /> </taskdef> <instrument verbose="true"> <fileset dir="${classes.dir}/bean"> <include name="BlobBEAN.class" /> <!-- 每次修改BlobBEAN的代码后都需要重新运行ant,否则这个ant会失效 --> </fileset> </instrument> </target> </project>
- Test.java
package action; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.io.IOUtils; import org.hibernate.Session; import org.hibernate.Transaction; import bean.BlobBEAN; import util.HibernateSessionUtil; public class Test { public static void main(String[] args) { // Test.save(); Test.load(); } private static void save() { Session session = null; Transaction tran = null; try { session = HibernateSessionUtil.getSession(); tran = session.beginTransaction(); BlobBEAN blobBean = new BlobBEAN(); blobBean.setBlobId(1); // Text类型 StringBuffer str = new StringBuffer(); for (int i = 0; i < 10000; i++) { str.append("abcabc"); } blobBean.setContent(str.toString()); // Image类型 String path = "F:\\123.jpg"; InputStream inputStream = new FileInputStream(new File(path)); byte[] imaBytes = IOUtils.toByteArray(inputStream); blobBean.setImage(imaBytes); session.save(blobBean); tran.commit(); } catch (Exception e) { e.printStackTrace(); tran.rollback(); } finally { HibernateSessionUtil.closeSession(); } } private static void load() { Session session = null; Transaction tran = null; try { session = HibernateSessionUtil.getSession(); tran = session.beginTransaction(); BlobBEAN blob=(BlobBEAN)session.get(BlobBEAN.class, new Integer(1)); System.out.println(blob.getName()); //属性的延迟加载,加载任意一个大字段时,会加载所有的属性延迟字段。 String content=blob.getContent(); OutputStream out=new FileOutputStream(new File("F:\\123.txt")); IOUtils.write(content, out); out.flush(); out.close(); tran.commit(); } catch (Exception e) { e.printStackTrace(); tran.rollback(); } finally { HibernateSessionUtil.closeSession(); } } }