检索数据,也就是查询数据是在一个系统中必不可少的一个功能。检索数据时的2个问题:
- 不浪费内存:例如,Customer和Order是双向1-N的关系。当 Hibernate 从数据库中加载 Customer 对象时, 如果同时加载所有关联的 Order 对象, 而程序实际上仅仅需要访问 Customer 对象, 那么这些关联的 Order 对象就白白浪费了许多内存。
- 更高的查询效率:发送尽可能少的 SQL 语句。
由于篇幅原因,将内容分为了两部分:
Hibernate:检索策略的学习1 Hibernate:检索策略的学习2
第一部分讲解了类级别的检索策略以及1-N和N-N的检索策略,在第二部分中将学习N-1和1-1的检索策略,并对检索策略进行总结。
类级别的检索策略
知识点
类级别可选的检索策略包括立即检索和延迟检索, 默认为延迟检索。
- 立即检索: 立即加载检索方法指定的对象;
- 延迟检索: 延迟加载检索方法指定的对象。在使用具体的属性时,再进行加载。
类级别的检索策略可以通过
如果程序加载一个对象的目的是为了访问它的属性,可以采取立即检索;如果程序加载一个持久化对象的目的是仅仅为了获得它的引用,可以采用延迟检索,但需要注意懒加载异常(LazyInitializationException:简单理解该异常就是Hibernate在使用延迟加载时,并没有将数据实际查询出来,而只是得到了一个代理对象,当使用属性的时候才会去查询,而如果这个时候session关闭了,则会报该异常)!
下面通过一个例子来进行讲解:
Demo
在该Demo中,我们只需要使用一个Customer的对象即可,其中包含了id,name等属性。
延迟检索
首先我们来测试一下
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.nowjava.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.nowjava.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文件,在
<hibernate-mapping package="com.nowjava.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.nowjava.hibernate.strategy.Customer
1
AA
我们可以看到,当调用load方法时,会发送SQL语句,并且得到的不再是代理对象。这个时候就算我们在输出name属性之前将session关闭,也不会报错。
小结
上文中对Hibernate的类级别的检索策略进行了总结,当然这些是建立在有一定基础的前提下。 需要注意的是:
- 无论
- 若
- 由 Hibernate 在运行时采用 CGLIB 工具动态生成;
- Hibernate 创建代理类实例时,仅初始化其 OID 属性;
- 在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例。
1-N和N-N的检索策略
知识点
我在之前的博客中对1-N和N-N有过学习,所以我假设读者已经了解了Hibernate中关于1-N和N-N的映射。我们建立了Customer与Order的1-N关联关系,表示一个顾客可以有多个订单。
我们在映射文件中,用
- lazy: 主要决定orders 集合被初始化的时机。即到底是在加载Customer 对象时就被初始化, 还是在程序访问 orders 集合时被初始化。
- fetch: 取值为 “select” 或 “subselect” 时, 决定初始化 orders 的查询语句的形式;若取值为”join”, 则决定 orders 集合被初始化的时机
- 若把 fetch 设置为 “join”, lazy 属性将被忽略
lazy属性 (默认值true) | fetch属性 (默认值select) | 检索策略 |
---|---|---|
true | 未设置 (取默认值select) | 采用延迟检索策略,这是默认的检索策略,也是优先考虑使用的检索策略 |
false | 未设置 (取默认值select) | 采用立即索策略,当使用Hibernate二级缓存时可以考虑使用立即检索 |
extra | 未设置 (取默认值select) | 采用加强延迟检索策略,它尽可能的延迟orders集合被初始化的时机 |
true,extra or extra | 未设置 (取默认值select) | lazy属性决定采用的检索策略,即决定初始化orders集合的时机。fetch属性为select,意味 着通过select语句来初始化orders的集合,形式为SELECT * FROM orders WHERE customer _id IN (1,2,3,4) |
true,extra or extra | subselect | lazy属性决定采用的检索策略,即决定初始化orders集合的时机。fetch属性为subselect,意味 着通过subselect语句来初始化orders的集合,形式为SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers) |
true | join | 采采用迫切左外连接策略 |
Lazy
我们现在开始研究一下关于
Demo
首先我们看一下延迟检索,也就是
@Test
public void testOne2ManyLevelStrategy() {
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
System.out.println(customer.getOrders().getClass());
System.out.println(customer.getOrders().size());
}
下面是控制的输出结果
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
class org.hibernate.collection.internal.PersistentSet
Hibernate:
select
orders0_.CUSTOMER_ID as CUSTOMER3_0_1_,
orders0_.ORDER_ID as ORDER_ID1_1_1_,
orders0_.ORDER_ID as ORDER_ID1_1_0_,
orders0_.ORDER_NAME as ORDER_NA2_1_0_,
orders0_.CUSTOMER_ID as CUSTOMER3_1_0_
from
ORDERS orders0_
where
orders0_.CUSTOMER_ID=?
3
从结果中可以明显的看出,Hibernate使用了延迟检索。其中的orders并没有初始化,而是返回了一个集合代理对象。当我们通过customer.getOrders().size()这段代码真正要使用orders集合的时候,才发送SQL语句进行查询。
在延迟检索(lazy属性值为true)集合属性时,Hibernate在以下情况下初始化集合代理类实例:
- 应用程序第一次访问集合属性: iterator(), size(), isEmpty(), contains() 等方法
- 通过 Hibernate.initialize() 静态方法显式初始化
下面我们将