Hibernate 检索策略的学习

检索数据也就是查询数据的时候存在两个问题:
1.不浪费内存:例如,Customer和Order是双向1-N的关系。当 Hibernate 从数据库中加载 Customer 对象时, 如果同时加载所有关联的 Order 对象, 而程序实际上仅仅需要访问 Customer 对象, 那么这些关联的 Order 对象就白白浪费了许多内存。
2.更高的查询效率:发送尽可能少的 SQL 语句。

延伸:Session的get()方法与load()方法的比较
1.都是按照OID查询指定对象
2.记录不存在的处理方式:get返回null, load()抛出HiberbateException异常
3.get()直接返回持久化对象,总是立即加载
load()支持延迟加载

一.检索策略

1.分类:

立即检索:
立即加载与当前对象关联的对象,需要执行多条SQL语句
延迟检索:
不立即加载与当前对象关联的对象,当第一次访问关联对象的时候才加载其信息
迫切左外连接检索:
通过左外连接加载与当前对象关联的对象,为立即检索策略,但是执行的sql语句少,只执行一条select连接查询语句

2.检索的方法

通过Session的get和load方法加载指定的OID的对象
通过HQL,QBC等检索方法加载满足条件的对象

3.检索的作用于包括:

类级别
作用于当前对象,设置对当前对象是立即检索还是延迟检索。默认是延迟检索,只对load()有效
关联级别
设置对关联对象是立即检索,延迟检索还是迫切左外连接检索,默认是延迟检索

二.类级别的检索策略

类级别的检索策略可以通过 元素的 lazy 属性进行设置

如果程序加载一个对象的目的是为了访问它的属性,可以采取立即检索;如果程序加载一个持久化对象的目的是仅仅为了获得它的引用,可以采用延迟检索,但需要注意懒加载异常(LazyInitializationException:简单理解该异常就是Hibernate在使用延迟加载时,并没有将数据实际查询出来,而只是得到了一个代理对象,当使用属性的时候才会去查询,而如果这个时候session关闭了,则会报该异常)!
下面通过一个例子来进行讲解:
在该Demo中,我们只需要使用一个Customer的对象即可,其中包含了id,name等属性。
延迟检索
首先我们来测试一下元素的lazy属性为true的情况,也就是默认情况(不设置)。

public class HibernateTest {

    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;
    @Before
    public void init() {
        Configuration configuration = new Configuration().configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties())
                .buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);

        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }
    @After
    public void destroy() {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }
    @Test
    public void testClassLevelStrategy() {
        Customer customer = (Customer) session.load(Customer.class, 1);
        System.out.println(customer.getClass());
        System.out.println(customer.getCustomerId());
        System.out.println(customer.getCustomerName());
    }
}

看一下上面的代码,该代码是利用Junit进行测试(关于Junit的知识在这不多说,并不是重点)。其中init方法是对SessionFactory、Session等进行初始化,destroy方法是进行关闭。

当我们运行testClassLevelStrategy()方法时,会得到以下输出结果:

class com.atguigu.hibernate.strategy.Customer_$$_javassist_1
1
Hibernate:
    select
        customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
        customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
    from
        CUSTOMERS customer0_
    where
        customer0_.CUSTOMER_ID=?
AA

通过控制台的输出结果,我们可以清楚的看到,当我们执行session.load(Customer.class, 1)方法时,Hibernate并没有发送SQL语句,而只是返回了一个对象,通过调用getClass()方法,可以看到该对象class com.atguigu.hibernate.strategy.Customer_$$_javassist_1是一个代理对象,并且当调用customer.getCustomerId()获取ID的时候,也没有发送SQL语句;当我们这个再调用customer.getCustomerName()方法来得到name的时候,这个时候才发送了SQL语句进行真正的查询,并且WHERE条件中带上的就是ID。

如果我们在System.out.println(customer.getCustomerName());语句前插入session.close()将Session关闭,就能看到之前上文中提到的懒加载异常。

立即检索
为了让Customer类可以立即检索,我们要修改Customer.hbm.xml文件,在标签中加入lazy=”false”。

<hibernate-mapping package="com.atguigu.hibernate.strategy">

    <class name="Customer" table="CUSTOMERS" lazy="false">

        <id name="customerId" type="java.lang.Integer">
            <column name="CUSTOMER_ID" />
            <generator class="native" />
        </id>

这个时候,我们再运行之前的单元测试代码,控制台会得到以下输出结果:

Hibernate:
    select
        customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
        customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
    from
        CUSTOMERS customer0_
    where
        customer0_.CUSTOMER_ID=?
class com.atguigu.hibernate.strategy.Customer
1
AA

我们可以看到,当调用load方法时,会发送SQL语句,并且得到的不再是代理对象。这个时候就算我们在输出name属性之前将session关闭,也不会报错。

小结
上文中对Hibernate的类级别的检索策略进行了总结,当然这些是建立在有一定基础的前提下。需要注意的是:

-无论元素的lazy 属性是true 还是false,Session 的get() 方法及Query 的list() 方法在类级别总是使用立即检索策略。
-若 元素的 lazy 属性为 true 或取默认值, Session 的 load() 方法不会执行查询数据表的 SELECT 语句,仅返回代理类对象的实例,该代理类实例有如下特征:
由 Hibernate 在运行时采用 CGLIB 工具动态生成;
Hibernate 创建代理类实例时,仅初始化其 OID 属性;
在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例。

三.关联级别的检索策略

1.一多关联检索策略

我们在映射文件中,用元素来配置1-N关联以及N-N关联关系。元素有lazy、fetch和batch-size属性。
lazy: 主要决定orders 集合被初始化的时机。即到底是在加载Customer 对象时就被初始化, 还是在程序访问 orders 集合时被初始化。
fetch: 取值为 “select” 或 “subselect” 时, 决定初始化 orders 的查询语句的形式;若取值为”join”, 则决定 orders 集合被初始化的时机
若把 fetch 设置为 “join”, lazy 属性将被忽略
batch-size 属性:用来为延迟检索策略或立即检索策略设定批量检索的数量. 批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能。

鉴于一多关联关系使用较多,这里以一多关系来讲解关联级别的检索策略。
以Grade(班级)和Student的关系为例:

List<Grade> list=session.createQuery("from Grade").list();//1

延迟检索时
将延加载当前对象的关联对象,班级类的映射文件配置如下:

<set name="students" cascade="save-update" inverse="true" lazy="true">

注:lazy=”true”可以不写,因为默认的就是true
此时执行第一行代码的时候,将产生一条SQL语句。只查询班级信息,而不查询关联的学生的信息:

select * from GRADE

立即检索
将加载当前对象的关联对象,班级类的映射文件如下:

<set name="students" cascade="save-update" inverse="true" lazy="false">

此时在执行第一行代码的时候,如果有n个班级信息,将会产生n+1个sql语句,此时不仅加载班级信息,还加载与班级关联的学生信息:

select * from GRADE
select * from STUDENT s where s.GID=110701
select * from STUDENT s where s.GID=110702

要解决n条信息,产生n+1条SQL语句的问题,可以考虑设置两个属性:batch-size,fetch;

2.配置batch-size

以Grade(班级)和Student的关系为例:

List<Grade> list=session.createQuery("from Grade").list();//1

通过设置batch-size批量检索的数量,来减少select语句的数量,减少访问数据库的次数,提高检索的性能。
班级类的映射配置文件改成如下:

< set name="students" cascade="save-update" inverse="true" lazy="false" batch-size="3">

batch-size默认的值是1,合理取值是2-10

3.配置fetch

fetch关键字表示加载关联对象查询语句的形式,以及加载关联对象的事件,取值如下:
select:加载关联对象的时候通过select语句实现,默认值
subselect:加载关联对象的时候通过带子查询语句实现
jion:采用迫切左外连接检索所有关联对象。‘

其中select和subselect适用于立即检索和延迟检索,join仅适用于立即检索。
select和subselect决定加载关联对象时候查询语句的形式,join还决定了加载关联对象的时机。
注意:
fetch=”join”仅适用于立即查询,lazy属性将被忽略,但是对list()方法不起作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值