Hibernate入门第十二讲——Hibernate的检索方式

Hibernate检索方式的概述

我们在对数据库的操作中,最常用的就是select,那么使用Hibernate如何进行select操作呢?本文就来徐徐道来。在Hibernate中提供了很多种的查询的方式,归纳起来可分为五种。

  • OID检索方式:按照对象的OID来检索对象;
  • 对象导航图检索方式:根据已加载的对象导航到其它对象;
  • HQL检索方式:使用面向对象的HQL查询语言;
  • QBC检索方式:使用QBC(Query by Criteria)API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口;
  • 本地SQL检索方式:使用本地数据库的SQL查询语句。

下面我就来一一介绍Hibernate中这5种检索方式。

Hibernate中的五种检索方式

OID检索方式

哎妈啊!这种方式用老多遍了,我都懒得说了。所谓OID检索,就是Hibernate根据对象的OID(主键)进行检索。在Hibernate中,我们可通过get或load方法根据OID来查询指定的对象。

  • 使用get方法

    Customer customer = session.get(Customer.class, 1l);
    
  • 使用load方法

    Customer customer = session.load(Customer.class, 1l);
    

对象导航图检索方式

所谓的对象导航图检索方式就是通过在Hibernate中进行映射关系,然后在Hibernate操作时,可以通过导航方式得到其关联的持久化对象信息。说得更容易理解一点就是:对象导航图检索方式就是Hibernate根据一个已经查询到的对象,获得其关联对象的一种查询方式。

LinkMan linkMan = session.get(LinkMan.class, 1l);
Customer customer  = linkMan.getCustomer();

或者

Customer customer = session.get(Customer.class, 2l);
Set<LinkMan> linkMans = customer.getLinkMans();

HQL检索方式

HQL是我们在Hibernate中最常用的一种检索方式。HQL(Hibernate Query Language)提供了更加丰富灵活、更为强大的查询能力,因此Hibernate将HQL查询方式立为官方推荐的标准查询方式,HQL查询在涵盖Criteria查询的所有功能的前提下,提供了类似标准SQL语句的查询方式,同时也提供了更加面向对象的封装。完整的HQL语句形式如下:

select/update/delete ...... from ...... where ...... group by ...... having ...... order by ...... asc/desc

其中的update/delete为Hibernate3中所新添加的功能,可见HQL查询非常类似于标准SQL查询。HQL检索的基本步骤:

  1. 得到Session;
  2. 编写HQL语句;
  3. 通过session.createQuery(HQL语句)创建一个Query对象;
  4. 为Query对象设置条件参数;
  5. 执行list()方法查询所有,它返回的是一个List集合或者执行uniqueResult()方法返回一个唯一的查询结果。

环境搭建

首先肯定是搭建好Hibernate的开发环境啦!笔者在此也不过多赘述,读者自行实践,聪明的读者要是有心的话,看过我的《Hibernate入门第十讲——Hibernate的一对多关联映射》这篇文章,自然就会知道如何搭建了。然后,我就来准备一些数据,不然查什么呢?我是这样做的:

  • 添加一个名叫张小敬的客户,在他名下创建10个联系人;
  • 再添加一个名叫鸣太子的客户,在他名下创建10个联系人;
  • 最后再添加一个名叫二柱子的客户,在他名下创建10个联系人。

怎么做到的呢?在src目录下新建一个com.meimeixia.hibernate.demo01包,在该包下编写一个HQLTest单元测试类,并在该包下编写如下方法:

package com.meimeixia.hibernate.demo01;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.domain.LinkMan;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * HQL查询方式的测试类
 * @author liayun
 *
 */
public class HQLTest {
	
	@Test
	public void demo01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		//创建一个客户
		Customer customer = new Customer();
		//customer.setCust_name("张小敬");
		//customer.setCust_name("鸣太子");
		customer.setCust_name("二柱子");
		
		for (int i = 1; i <= 10; i++) {
			LinkMan linkMan = new LinkMan();
			//linkMan.setLkm_name("檀其" + i);
			//linkMan.setLkm_name("自来也" + i);
			linkMan.setLkm_name("大蛇丸" + i);
			//设置双向的关联关系
			linkMan.setCustomer(customer);
			customer.getLinkMans().add(linkMan);
			
			session.save(linkMan);
		}
		session.save(customer);
		
		tx.commit();
	}
	
}

运行以上方法之后,我们所想要的数据就准备好了,下面就正式进入正题。

基本检索

基本检索就是from 类名,from是关键字,后面是类名,关键字是不区分大小写的,但是类名是区分的。以码明示,在HQLTest单元测试类中编写如下单元测试方法:
Hibernate入门第十二讲——Hibernate的检索方式_第1张图片
为了更加方便在Eclipse控制台看到打印的结果,我们最好在Customer类中重写toString()方法。
Hibernate入门第十二讲——Hibernate的检索方式_第2张图片
温馨提示:SQL中支持*号的写法,例如select * from cst_customer;,但是在HQL中是不支持这种写法的。
Hibernate入门第十二讲——Hibernate的检索方式_第3张图片

别名检索

在写HQL查询语句的时候,还能给类起个别名,所以,你可以像下面这样写代码。

/**
 * 别名查询
 */
@Test
public void demo03() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//别名查询
	Query query = session.createQuery("from Customer c");
	List<Customer> list = query.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

除此之外,你还能这样写你的代码。
Hibernate入门第十二讲——Hibernate的检索方式_第4张图片

排序检索

现在我们的需求是查询客户时,根据客户的ID进行升序排序。以码明示,在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 排序查询
 */
@Test
public void demo04() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//排序查询
	//默认升序排列
	List<Customer> list = session.createQuery("from Customer order by cust_id").list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

温馨提示:HQL排序检索时,默认是升序排列,跟SQL里面的排序一样,升序使用的是ASC,降序使用的是DESC。所以,如果查询客户时,要根据客户的ID进行降序排序,那么就要这样写代码了。

/**
 * 排序查询
 */
@Test
public void demo04() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//排序查询
	//默认升序排列
	//List list = session.createQuery("from Customer order by cust_id").list();
	
	//设置降序排列,升序就使用asc,降序就使用desc,跟sql里面的排序是一样的
	List<Customer> list = session.createQuery("from Customer order by cust_id desc").list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

条件检索

我首先来讲根据位置来绑定HQL语句参数的条件检索。如果想要查询名称叫张小敬的客户,那么你有可能在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 条件查询
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//条件查询
	//1. 按位置绑定:根据参数的位置进行绑定
	Query query = session.createQuery("from Customer where cust_name = ?");
	//只设置一个条件
	query.setParameter(0, "张小敬");
	List<Customer> list = query.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

在上面的代码中,也只是设置了一个条件,那如果要设置多个条件呢,该咋设置?例如,现在想要查询姓张并且客户信息来源是知乎的客户,那么你有可能会在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 条件查询
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//条件查询
	//1. 按位置绑定:根据参数的位置进行绑定
	Query query = session.createQuery("from Customer where cust_source = ? and cust_name like ?");
	//设置多个条件
	query.setParameter(0, "知乎");
	query.setParameter(1, "张%");
	List<Customer> list = query.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

接着我来讲讲根据名称来绑定HQL语句参数的条件检索。如果现在还是想要查询姓张并且客户信息来源是知乎的客户,那么以上demo05方法应修改为如下。

/**
 * 条件查询
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//条件查询
	//2. 按名称绑定:
	Query query = session.createQuery("from Customer where cust_source = :aaa and cust_name like :bbb");
	//设置参数
	query.setParameter("aaa", "知乎");
	query.setParameter("bbb", "张%");
	List<Customer> list = query.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

分页检索

假设现在我们的需求是查询联系人时,每页显示10条记录,但要得到第二页的数据,那么你有可能在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 分页查询
 */
@Test
public void demo06() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	Query query = session.createQuery("from LinkMan");
	//每页显示10条记录,我们要得到第二页数据
	query.setFirstResult(10);//从第几条开始,即开始位置,从0开始计数
	query.setMaxResults(10);//每页显示多少条记录
	List<LinkMan> list = query.list();
	
	for (LinkMan linkMan : list) {
		System.out.println(linkMan);
	}
	
	tx.commit();
}

分组统计检索

先看我们的第一个需求:统计一共有多少个客户。以码明示,在HQLTest单元测试类中编写如下单元测试方法:
Hibernate入门第十二讲——Hibernate的检索方式_第5张图片
再看我们的第二个需求:分组统计每一个客户信息来源有多少客户。此时,以上demo07的方法可能就要修改为:
Hibernate入门第十二讲——Hibernate的检索方式_第6张图片
如果查询增加一点难度,想要分组统计每一个客户信息来源有多少客户,并且客户人数至少要是2人,要想解决这个需求,那么以上demo07的方法可能就要修改为:
Hibernate入门第十二讲——Hibernate的检索方式_第7张图片
其他一些聚合函数,例如max()、min()、avg()以及sum()的使用方法也就和count()一样,在此并不过多赘述。

投影检索

啥叫投影检索啊?投影检索就是查询对象的某个或某些属性,而不是全部属性。这儿我也只是主要讲解关于实体类中部分属性的查询,这样我们就可以使用投影检索将部分属性封装到对象中。下面我步步为营地讲解投影检索。
一开始,我们的需求可能是这样的:查询出所有客户的名称,故我们可能会在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 投影查询
 */
@Test
public void demo08() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//投影查询
	//查询单个属性
	List<Object> list = session.createQuery("select c.cust_name from Customer c").list();
	System.out.println(list);
	
	tx.commit();
}

运行以上方法,控制台打印出[张小敬, 鸣太子, 二柱子]这样的字符串,那么可以得出结论:如果只是查询一个列,那么得到的结果便是List
接着,我们的需求又可能会变成这样:查询所有客户的名称和客户信息来源。故可将以上demo08方法改为:

/**
 * 投影查询
 */
@Test
public void demo08() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//投影查询
	//查询多个属性
	List<Object[]> list = session.createQuery("select c.cust_name,c.cust_source from Customer c").list();
	for (Object[] objects : list) {
		System.out.println(Arrays.toString(objects));
	}
	
	tx.commit();
}

从以上方法中可得知,如果是查询多列,那么得到的结果是List
最后,我们可能想到使用投影查询将查询的结果封装到Customer对象中。故可将以上demo08方法改为:
Hibernate入门第十二讲——Hibernate的检索方式_第8张图片
温馨提示:我们必须在Customer类中提供对应属性的构造方法,也要有无参数构造。所以,Customer类中的内容就应该是下面这样的。
Hibernate入门第十二讲——Hibernate的检索方式_第9张图片

命名检索

现在我就来讲讲命名检索,我们可以将HQL语句先定义出来,在使用时通过session.getNamedQuery(hqlName);得到一个Query对象,然后再执行检索。那么问题就来了,这个HQL语句到底定义在什么位置呢?如果你有映射配置文件,那么当前的HQL操作是针对哪一个实体进行操作的,就在哪一个实体的映射配置文件中进行声明,声明就像下面这样。
Hibernate入门第十二讲——Hibernate的检索方式_第10张图片
当然了,如果你是使用注解来描述PO类的配置,那么你可以直接在PO类中使用@NamedQuery注解来声明。这又是另外一个话题了,因为我们得会Hibernate的注解开发,之前我们一直都是使用映射配置文件,这儿突然来使用注解进行声明,未免太过唐突,所以,这儿我就不讲解这种方式了。感兴趣的童鞋,可以自己实践实践!
此时我们是准备要查询所有客户的,所以,可以在HQLTest单元测试类中编写如下单元测试方法:

/**
 * 命名查询
 */
@Test
public void demo09() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//命名查询
	Query query = session.getNamedQuery("myHql");
	List<Customer> list = query.list();
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

关于命名查询,我就讲到这里。

QBC检索方式

QBC(Query by Criteria),它是一种更加面向对象的检索方式。使用QBC检索的步骤如下:

  • 通过Session的createCriteria方法得到一个Criteria对象,即session.createCriteria();
  • 设定条件,每个Criterion实例就代表一个条件,它的获取可以通过Restrictions类提供的静态方法得到,然后可通过Criteria的add方法添加查询条件;
  • 调用Criterion的list方法进行查询,即criterion.list()。

基本检索

有一个需求:查询所有的Customer对象。以码明示,在com.meimeixia.hibernate.demo01包下编写一个QBCTest单元测试类,并在该类中编写如下测试方法:

package com.meimeixia.hibernate.demo01;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * 测试QBC的查询
 * @author liayun
 *
 */
public class QBCTest {

	/**
	 * 简单的查询
	 */
	@Test
	public void demo01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		//获得Criteria的对象
		Criteria criteria = session.createCriteria(Customer.class);
		List<Customer> list = criteria.list();
		
		for (Customer customer : list) {
			System.out.println(customer);
		}
		
		tx.commit();
	}
	
}

排序检索

有这样一个需求:查询客户信息,并根据客户的ID进行排序。以码明示,在QBCTest单元测试类中编写如下测试方法:

/**
 * 排序查询
 */
@Test
public void demo02() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//排序查询
	Criteria criteria = session.createCriteria(Customer.class);
	//criteria.addOrder(Order.asc("cust_id"));//asc是升序
	criteria.addOrder(Order.desc("cust_id"));//desc是降序
	List<Customer> list = criteria.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

条件检索

有这样一个需求:查询客户信息来源为知乎的客户。以码明示,在QBCTest单元测试类中编写如下测试方法:

/**
 * 条件查询
 */
@Test
public void demo03() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//条件查询
	Criteria criteria = session.createCriteria(Customer.class);
	//设置条件,只设置一个条件
	/*
	 * =	eq
	 * > 	gt
	 * >=  	ge
	 * < 	lt
	 * <=	le
	 * <>	ne
	 * like
	 * in
	 * and
	 * or
	 */
	
	//下面只设置一个条件
	criteria.add(Restrictions.eq("cust_source", "知乎"));
	
	List<Customer> list = criteria.list();
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

现在又有了这样一个需求:查询姓张并且客户信息来源为知乎的客户。这时,可将QBCTest单元测试类中的demo03方法修改为:

/**
 * 条件查询
 */
@Test
public void demo03() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//条件查询
	Criteria criteria = session.createCriteria(Customer.class);
	//设置条件
	/*
	 * =	eq
	 * > 	gt
	 * >=  	ge
	 * < 	lt
	 * <=	le
	 * <>	ne
	 * like
	 * in
	 * and
	 * or
	 */
	
	//下面设置的是多个条件,
	criteria.add(Restrictions.eq("cust_source", "知乎"));
	criteria.add(Restrictions.like("cust_name", "张%"));
	
	List<Customer> list = criteria.list();
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

分页检索

有这样一个需求:查询联系人时,每页显示10条记录,但我们要得到第二页的数据,那么你有可能在QBCTest单元测试类中编写如下单元测试方法:

/**
 * 分页查询
 */
@Test
public void demo04() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//分页查询
	Criteria criteria = session.createCriteria(LinkMan.class);
	criteria.setFirstResult(10);//从第几条开始,即开始位置,从0开始计数
	criteria.setMaxResults(10);//每页显示多少条记录
	List<LinkMan> list = criteria.list();
	
	for (LinkMan linkMan : list) {
		System.out.println(linkMan);
	}
	
	tx.commit();
}

统计检索

有这样一个需求:统计客户总数。以码明示,在QBCTest单元测试类中编写如下单元测试方法:

/**
 * 统计查询
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	Criteria criteria = session.createCriteria(Customer.class);
	/*
	 * Criteria对象的方法有:
	 * add:加的是普通的条件(where后面的条件)
	 * addOrder:排序
	 * setProjection:弄那些聚合函数和group by having
	 */
	criteria.setProjection(Projections.rowCount());//统计总行数,可以理解为count(*)
	Long count = (Long) criteria.uniqueResult();
	System.out.println(count);

	tx.commit();
}

我想在实际开发中,像这样子的统计检索应该用的不多,使用最多的还要数HQL分组统计检索,可能这种方式比较接近SQL查询,可以直观地让人感受到Hibernate底层向数据库发送了什么样子的SQL查询语句。

离线条件检索

Hibernate框架支持在运行时动态生成查询语句,DetachedCriteria对象可以脱离Session而使用。之前我们在三层架构之间传递用户提交的参数可能是这样的:
Hibernate入门第十二讲——Hibernate的检索方式_第11张图片
但使用离线条件对象进行检索时,情况有可能是这样的:
Hibernate入门第十二讲——Hibernate的检索方式_第12张图片
现在有这样一个需求:查询姓张的客户。以码明示,在QBCTest单元测试类中编写如下单元测试方法:

/**
 * 离线条件查询
 */
@Test
public void demo06() {
	//假设这个离线条件查询对象是从web层传递过来的
	DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
	detachedCriteria.add(Restrictions.like("cust_name", "李%"));
	
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//让离线条件查询对象跟Session绑定一下,就会得到一个Criteria对象,然后调用Criteria对象的方法
	Criteria criteria = detachedCriteria.getExecutableCriteria(session);
	
	List<Customer> list = criteria.list();
	
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

本地SQL检索方式

有这样一个需求:查询所有的Customer对象。以码明示,在com.meimeixia.hibernate.demo01包中编写一个SQLTest单元测试类,并在类中编写如下方法:

package com.meimeixia.hibernate.demo01;

import java.util.List;

import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * SQL查询
 * @author liayun
 *
 */
public class SQLTest {

	@Test
	public void demo01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		/*
		SQLQuery query = session.createSQLQuery("select * from cst_customer");
		List list = query.list();
		for (Object[] objects : list) {
			System.out.println(Arrays.toString(objects));
		}
		*/
		
		SQLQuery query = session.createSQLQuery("select * from cst_customer");
		query.addEntity(Customer.class);//如何能得到一个对象呢?即将查询结果封装到指定对象。
		List<Customer> list = query.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		
		tx.commit();
	}
	
}

需要说明的是本地SQL查询也支持命名查询。那么问题就来了,这个本地SQL语句到底应定义在什么位置呢?如果你有映射配置文件,那么当前的SQL查询是针对哪一个实体进行操作的,就在哪一个实体的映射配置文件中进行声明,声明就像下面这样。
Hibernate入门第十二讲——Hibernate的检索方式_第13张图片
本地命名SQL还可使用注解来定义,但这里我就不讲了,感兴趣的童鞋,可以自己动手实践实践!
此时我们是准备要查询所有客户的,所以,可以在SQLTest单元测试类中编写如下单元测试方法:

@Test
public void demo02() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	SQLQuery query = (SQLQuery) session.getNamedQuery("mySQL");
	query.addEntity(Customer.class);
	List<Customer> list = query.list();
	for (Customer customer : list) {
		System.out.println(customer);
	}
	
	tx.commit();
}

你可能感兴趣的:(Hibernate框架,Hibernate框架学习)