(十六)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();
        }

    }

}


      

原文地址:https://www.cnblogs.com/shyroke/p/6905609.html