类级别的检索策略
类级别的检索策略可以通过class元素的lazy属性来设置,该属性默认为true,即为延迟检索。
延迟检索只对load方式有效,对get和query的查询无效。
延迟减速简单来说就是通过CGLIB动态生成代理,加载时仅加载对象的OID属性,在调用该对象的其他属性时,代理像数据库发送sql语句加载对象,测试一下:
<class name="cn.net.bysoft.model1.Customer" table="CUSTOMERS" lazy="true">
@Test
public void testLazyIsTrue() {
// 在延迟加载的情况下load一个customer对象。
// 由hibernate生成代理,只初始化customer的id属性。
Customer customer = (Customer) session.load(Customer.class, 1);
// 使用id的操作不会发送sql语句到数据库。
System.out.println("使用id属性之前不会发送sql语句去数据库查询," + customer.getId());
System.out.println("===================");
// 使用其他属性时,在发送sql数据。
System.out.println("使用name属性之前会发送sql语句去数据库查询," + customer.getName());
}
如果将lazy属性设置为false,则在load方法调用时,立即发送sql语句到数据库查询对象,关闭延迟检索,也叫立即检索,进行一个测试:
<class name="cn.net.bysoft.model1.Customer" table="CUSTOMERS" lazy="false">
@Test
public void testLazyIsFalse() {
// 立即检索costomer对象,在load时候发送sql语句到数据库进行查询。
Customer customer = (Customer) session.load(Customer.class, 1);
System.out.println(customer.getId());
System.out.println(customer.getName());
}
一对多、多对多的检索策略
一对多与多对多的检索策略有三个属性:lazy、batch-size和fetch。
在配置文件中,对set元素设置lazy属性可以用来决定set的初始化方式,该属性可以为true、false和extra。首先来看看lazy等于true。
在加载对象时,对象中若存在集合属性,也是默认进行延迟加载的(lazy=true),也就是说在不使用集合对象时,hibernate不会发送sql语句去数据库查询,测试一下:
<!-- customer.hbm.xml配置文件中的set元素 -->
<set name="orders" table="ORDERS" inverse="true" cascade="delete" lazy="true">
@Test
public void testOntToManyLazyIsTrue()
{
Customer customer = (Customer)session.get(Customer.class, 1);
Set<Order> order = customer.getOrders();
// 在不使用集合中的对象时,将不会发送sql语句到数据库去查询order。
System.out.println(order.getClass());
}
设置lazy=false则会在加载customer时也发送sql语句去初始化order集合。
<!-- customer.hbm.xml配置文件中的set元素 -->
<set name="orders" table="ORDERS" inverse="true" cascade="delete" lazy="false">
@Test
public void testOntToManyLazyIsFalse() {
// 发送查询customer和order的sql语句。
Customer customer = (Customer) session.get(Customer.class, 1);
}
将lazy设置成extra可以增强延迟检索,比如,在lazy等于true时,调用集合的size()方法也会发送查询集合数据的sql语句,而设置成extran后,在使用集合的某些方法时,将尽可能的发送合理的sql语句,也就是说能不初始化集合就不初始化集合。测试一下:
<!-- customer.hbm.xml配置文件中的set元素 -->
<set name="orders" table="ORDERS" inverse="true" cascade="delete" lazy="extra">
@Test
public void testOntToManyLazyIsExtra() {
Customer customer = (Customer) session.get(Customer.class, 1);
Set<Order> orders = customer.getOrders();
// 使用集合的size属性时,sql语句只发送select count(id) from ...查询总行数,而不会查询全部order数据。
System.out.println(orders.size());
}
接下来看看batch-size的作用,该属性会设定批量检索的数量,用代码来理解一下,首先不设置batch-size,便利每个customer中的order数量,在for循环中,每一个getOrders().size()都会发送一次sql语句,下面是代码和数据库中的测试数据,第一张图是customer表,第二张图是order表:
<set name="orders" table="ORDERS" inverse="true" cascade="delete" lazy="true">
@Test
public void testBatchSize() {
// 查询所有的customer
List<Customer> customers = session.createQuery("from Customer c")
.list();
System.out.println(customers.size());
// 循环获得每个customer的orders。
// 在不设置batchsize的情况下每次都会发送sql语句到数据库去查询。
for (Customer customer : customers) {
if (customer.getOrders() != null) {
System.out.println(customer.getOrders().size());
}
}
}
显然在for循环中,每次调用.size()都发送sql语句到数据库查询不是最优的办法,这是,batch-size就有用武之地了。可以看到,测试数据中,每一个customer都有3个订单,那么将batch-size设置成3,将使用in(?,?,?)的sql语句去数据库查询,in中的id数量就是batch-size设置的值,结果只发送一条sql就可以了:
<set name="orders" table="ORDERS" inverse="true" cascade="delete" lazy="true" batch-size="3">
接下来就是fetch属性了,该属性有三个值,分别是:select、subselect和join。该属性用于设置集合的联合查询方式。
其中select和subselect可以会决定集合的查询形式,上面在做batch-size的测试时,由于没有设置fetch属性,fetch的默认值是select,查询order表的sql语句是... in (customerid1, customerid2, customerid3),现在将fetch的属性改为subselect在看看会输出什么样的sql语句。
<set name="orders" table="ORDERS" inverse="true" cascade="delete"
lazy="true" batch-size="3" fetch="subselect">
设置成subselect可以发现初始化集合时的条件由普通的查询条件变为了子查询方式,这就是fetch属性的作用,注意,在subselect模式下,我们设置的batch-size属性无效。
还有一个join,设置后hibernate在初始化集合时会采用“迫切左外连接”的方式,在该模式下,设置的lazy属性无效,但是hql查询会忽略fetch=join,依旧使用lazy加载策略。
进行一个测试:
<set name="orders" table="ORDERS" inverse="true" cascade="delete"
lazy="true" batch-size="3" fetch="join">
@Test
public void testFetchJoin() {
// 设置了fetch等于join后,采用迫切左外连接查询
// 会通过left join一次查找出所有的customer和它的orders
// 忽略lazy属性。
Customer customer = (Customer) session.get(Customer.class, 1);
Set<Order> orders = customer.getOrders();
System.out.println(orders.size());
}
多对一和一对一的检索策略
和set元素一样,many-to-one也有lazy属性和fetch属性。lazy属性有三个值,分别是:proxy、no-proxy和false。proxy代表延迟加载,false代表立即加载。fetch属性有两个值,分别是:select和join。select代表普通的抓取方式,join代表使用迫切左外连接的抓取方式。
使用join将忽略lazy=proxy。
进行一下测试,获得一个order,默认情况下,不会对order对应的customer进行检索,记得把customer配置文件的lazy设置成true:
<!-- order.hbm.xml的多对一配置 -->
<many-to-one name="customer" class="cn.net.bysoft.model1.Customer"
column="CUSTOMER_ID">
</many-to-one>
@Test
public void testManyToOne() {
// 查询一个order对象
Order order = (Order) session.get(Order.class, 1);
System.out.println(order.getName());
}
当设置lazy=false时,会使用立即检索获得order对应的customer的数据。
<!-- order.hbm.xml的多对一配置 -->
<many-to-one name="customer" class="cn.net.bysoft.model1.Customer"
column="CUSTOMER_ID" lazy="false">
</many-to-one>
将fetch设置成join,结果如下:
<!-- order.hbm.xml的多对一配置 -->
<many-to-one name="customer" class="cn.net.bysoft.model1.Customer"
column="CUSTOMER_ID" lazy="false" fetch="join">
最后,在来看一个问题,在测试一对多的查询检索时做过的,如果将所有的order都查询出来,使用循环来获得每一个order的customer会怎么样呢?
@Test
public void testManyToOneBatchSize() {
// 查询一个order对象
List<Order> orders = session.createQuery("from Order o").list();
for(Order order : orders) {
System.out.println(order.getCustomer().getName());
}
}
由于测试数据中customer表有4条记录,所以可以看到打印了4个sql语句,那么能否只发送一个sql语句查询出这4个customer呢?可以,在customer.hbm.xml中的class元素设置batch-size属性就能做到了。
<class name="cn.net.bysoft.model1.Customer" table="CUSTOMERS" batch-size="4">
以上,就是hibernate检索策略的简单说明。