- Hibernate是DAO层技术,对数据的使用,查询是最为重要的。Hibernate的查询技术非常强大,支持原始SQL语句查询,支持QBC查询以及Hibernate特有的HQL查询。
- HQL,Hibernate Query Language,Hibernate查询语言,它和SQL非常相似,但是,HQL是面向对象的查询语言,而SQL是面向二维表的。HQL查询语句中使用的是类名和属性名,而SQL语句使用的是表名和字段名。
- 需要注意的是,在不使用类的别名的情况下,在HQL中直接使用字段名也是可以通过的。但是若使用“类别名.属性名"的形式,则不能够使用字段名。
- QBC,Query By Criteria,标准查询,一种比HQL更为面向对象的查询方法。
1 API
1.1 Query接口
- Hibernate进行HQL查询的接口,支持动态绑定参数的功能。使用Session对象的createQuery方法可获取Query对象。
- Query query = session.createQuery(hql);。
1.2 SQLQuery接口
- Hibernate进行SQL原生查询的接口,支持动态绑定参数的功能,是Query接口的子接口。使用Session对象的createSQLQuery()方法可获取SQLQuery对象。
- SQLQuery sqlQuery = session.createSQLQuery(sql);。
- 其查询出的结果对象默认为Object,当然,若结果为List,则其元素为Object。使用SQLQuery的addEntity(Xxx.class)方法,可以将其结果泛型设定为指定类型。
1.3 Criteria接口
- Criteria标准、准则,Hibernate进行Criteria查询的接口,与Query接口无关。使用Session对象的createCriteria()方法可获取Criteria对象。
- Criteria criteria = session.createCriteria(Xxx.class);。
2 分类查询
2.1 查询测试前的准备工作
2.1.1 定义查询的实体
-
下面查询举例,均是对实体类Student进行操作,其映射关系如下:
2.1.2 定义主配置文件
com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/test root 02000059 org.hibernate.dialect.MySQL5Dialect thread update true true 2.1.3 定义Hibernate工具类
public class HbnUtils { private static SessionFactory sessionFactory; public static Session getSession() { return getSessionFactory().getCurrentSession(); } private static SessionFactory getSessionFactory() { if(sessionFactory == null || sessionFactory.isClosed()) { sessionFactory = new Configuration().configure().buildSessionFactory(); } return sessionFactory; } }
2.1.4 定义测试类的setUp()
- 创建好测试类MyTest后,完成初始化方法setUp():
public class MyTest { private Session session; @Before public void setUp() { session = HbnUtils.getSession(); } }
2.1.5 准备测试数据
//完成数据插入 @Test public void test00() { try { session.beginTransaction(); for(int i = 0; i < 10; i ++) { Student student = new Student("n_" + i, 15 +i , 75 + i); session.save(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.2 查询所有
- 查询所有的特点是,其查询结果为List集合。
2.2.1 SQL查询
//使用SQLQuery查询所有 @Test public void test02_SQL() { try { session.beginTransaction(); String sql = "select * from t_student"; List
students = session.createSQLQuery(sql).addEntity(Student.class).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.2.2 HQL查询
//使用HQL查询所有 @Test public void test02_HQL() { try { session.beginTransaction(); String hql = "from Student"; List
students = session.createQuery(hql).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.2.3 QBC查询
//使用QBC查询所有 @Test public void test02_QBC() { try { session.beginTransaction(); List
students = session.createCriteria(Student.class).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.3 查询结果排序
-
排序,即order by,asc表示升序,desc表示降序。
2.3.1 SQL查询
//使用SQLQuery查询所有,并排序 @Test public void test03_SQL() { try { session.beginTransaction(); String sql = "select * from t_student order by sscore desc"; List
students = session.createSQLQuery(sql).addEntity(Student.class).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.3.2 HQL查询
//使用HQL查询所有,并排序 @Test public void test03_HQL() { try { session.beginTransaction(); String hql = "from Student order by score desc"; List
students = session.createQuery(hql).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.3.3 QBC查询
- 通过addOrder()方法可以向Criteria对象添加排序功能。Order类有两个静态方法desc()和asc(),分别用于降序和升序功能,它们的参数为排序属性名,注意非字段名。
//使用QBC查询所有,并排序 @Test public void test03_QBC() { try { session.beginTransaction(); List
students = session.createCriteria(Student.class).addOrder(Order.desc("score")).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.4 动态参数绑定查询
- 动态参数绑定查询,即参数的值是由代码传递来的,而非固定在查询中。
2.4.1 SQL查询
- 方式一:使用”?"占位符占位,setXxx()方法绑定参数:setXxx(int position, Object value)。方法名中的Xxx表示该位置参数的类型。position表示第几个占位符,从0开始计数。
//使用SQLQuery查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_SQL_setXxx() { try { session.beginTransaction(); String sql = "select * from t_student where sage > ? and sscore < ?"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setInteger(0, 20).setDouble(1, 98).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } - 方式二:使用别名占位,setXxx()绑定参数:setXxx (String name, Object value)。别名的命名规则为:冒号后跟别名,别名可以是任意名称。
//使用SQLQuery查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_SQL_setXxx2() { try { session.beginTransaction(); String sql = "select * from t_student where sage > :tage and sscore < :tscore"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setInteger("tage", 20).setDouble("tscore", 98).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } - 方式三:使用“?”占位符占位,setParameter()绑定参数:SetParameter(int position, Object value)。position表示第几个占位符,从0开始计数。
//使用SQLQuery查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_SQL_setParameter() { try { session.beginTransaction(); String sql = "select * from t_student where sage > ? and sscore < ?"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setParameter(0, 20).setParameter(1, 98).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } - 方式四:使用别名占位,setParameter()绑定参数:SetParameter(String name, Object value)。别名的命名规则为:冒号后跟别名,别名可以是任意名称。
//使用SQLQuery查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_SQL_setParameter2() { try { session.beginTransaction(); String sql = "select * from t_student where sage > :tage and sscore < :tscore"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setParameter("tage", 20).setParameter("tscore", 98).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.4.2 HQL查询
- 使用HQL查询时,以上四种为动态参数赋值的方式均可成立,其用法相同。
//使用HQL查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_HQL() { try { session.beginTransaction(); String hql = "from Student where sage > :tage and sscore < :tscore"; List
students = session.createQuery(hql) .setParameter("tage", 20).setParameter("tscore", 98).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.4.3 QBC查询
- 需要向Criteria对象添加限制条件Restrictions,即添加查询条件。(Restriction,中文意思:限制,约束。)Restrictions具有很多静态方法可用于表示关系比较。
-
注意double类型数据的写法。
//使用QBC查询年龄大于20岁,成绩小于98分的学生 @Test public void test04_QBC() { try { session.beginTransaction(); List
students = session.createCriteria(Student.class) .add(Restrictions.gt("age", 20)) //需要注意,这里不能够写成98 .add(Restrictions.lt("score", 98.0)).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.5 分页查询
- 分页查询的SQL语句:select * from … where …limit startIndex, pageSize。
- 其中,startIndex为开始索引,设置从总查询结果集的第几条记录作为本查询结果子集的开始,即本页的开始。总查询结果集从0开始计数。pageSize为该页所包含的记录数。
2.5.1 SQL查询
//使用SQLQuery查询出所有学生中从第4条开始的5名学生信息 @Test public void test05_SQL() { try { session.beginTransaction(); String sql = "select * from t_student limit ?, ?"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setParameter(0, 3).setParameter(1, 5).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.5.2 HQL查询
- Query接口中具有setFirstResult()方法,用于设置从总查询结果集的第几条记录作为本查询结果子集的开始,即本页的开始。总查询结果集从0开始计数。而setMaxResults()方法用于设置该页所包含的记录数。
//使用HQL查询出所有学生中从第4条开始的5名学生信息 @Test public void test5_HQL() { try { session.beginTransaction(); String hql = "from Student"; List
students = session.createQuery(hql) .setFirstResult(3).setMaxResults(5).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.5.3 QBC查询
- Criteria接口中也有setFirstResult()与setMaxResults()方法,其用法与意义和Query接口中的完全相同。由于Criteria与Query接口无关,所以这两组完全相同的方法也是没有关系的,仅仅是用法和写法上得相同而已。
//使用QBC查询出所有学生中从第4条开始的5名学生信息 @Test public void test05_QBC() { try { session.beginTransaction(); List
students = session.createCriteria(Student.class) .setFirstResult(3).setMaxResults(5).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.6 模糊查询
2.6.1 SQL查询
//使用SQLQuery查询出所有姓名中包含字符n的学生 @Test public void test06_SQL() { try { session.beginTransaction(); String sql = "select * from t_student where sname like :tname"; List
students = session.createSQLQuery(sql) .addEntity(Student.class).setParameter("tname", "%n%") .list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.6.2 HQL查询
//使用HQL查询出所有姓名中包含字符n的学生 @Test public void test06_HQL() { try { session.beginTransaction(); String hql = "from Student where name like :tname"; List
students = session.createQuery(hql) .setParameter("tname", "%n%") .list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.6.3 QBC查询
//使用QBC查询出所有姓名中包含字符n的学生 @Test public void test06_QBC() { try { session.beginTransaction(); List
students = session.createCriteria(Student.class) .add(Restrictions.like("name", "%n%")).list(); for(Student student : students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.7 唯一性查询
2.7.1 SQL查询
- SQLQuery接口的uniqueResult()方法表示其查询结果为一条数据,其返回值为Object。
//使用SQLQuery查询id为5的学生 @Test public void test07_SQL() { try { session.beginTransaction(); String sql = "select * from t_student where sid = ?"; Student student = (Student) session.createSQLQuery(sql) .addEntity(Student.class).setInteger(0, 5).uniqueResult(); System.out.println(student); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.7.2 HQL查询
//使用HQL查询id为5的学生 @Test public void test07_HQL() { try { session.beginTransaction(); String hql = "from Student where id = ?"; Student student = (Student) session.createQuery(hql) .setInteger(0, 5).uniqueResult(); System.out.println(student); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.7.3 QBC查询
//使用QBC查询id为5的学生 @Test public void test07_QBC() { try { session.beginTransaction(); Student student = (Student) session.createCriteria(Student.class) .add(Restrictions.eq("id", 5)).uniqueResult(); System.out.println(student); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.8 聚合函数查询
2.8.1 SQL查询
-
注意区分count()与count(name)的不同。count()与count(id)等价,均表示未空记录数,而count(name)则表示字段name的非空记录数。
//查询记录总数以及姓名非空的记录数 @Test public void test08_SQL() { try { session.beginTransaction(); String sql1 = "select count(*) from t_student"; Object total1 = session.createSQLQuery(sql1).uniqueResult(); System.out.println(total1); String sql2 = "select count(sname) from t_student"; Object total2 = session.createSQLQuery(sql2).uniqueResult(); System.out.println(total2); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.8.2 HQL查询
//查询记录总数以及姓名非空的记录数 @Test public void test08_HQL() { try { session.beginTransaction(); String hql1 = "select count(*) from Student"; Object total1 = session.createQuery(hql1).uniqueResult(); System.out.println(total1); String hql2 = "select count(sname) from Student"; Object total2 = session.createQuery(hql2).uniqueResult(); System.out.println(total2); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.8.3 QBC查询
-
Projection,中文意思:投影。Projection中包含很多聚合函数的静态方法。
//查询记录总数以及姓名非空的记录数 @Test public void test08_QBC() { try { session.beginTransaction(); Object total1 = session.createCriteria(Student.class) .setProjection(Projections.count("id")).uniqueResult(); System.out.println(total1); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }
2.9 投影查询
2.9.1 SQL查询
- 要求查询的字段的别名要与实体类的属性名相同,这样才能将结果转换为相应类的对象。
//使用SQLQuery查询所有学生的姓名与年龄 @Test public void test09_SQL() { try { session.beginTransaction(); String sql = "select sname name, sage age from t_student"; List
students = session.createSQLQuery(sql) .setResultTransformer(Transformers.aliasToBean(Student.class)).list(); for(Student student: students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } - aliasToBean()方法首先会创建一个空的Student对象,然后会将别名和Student属性名对比,再用查询出的值初始化创建的Student对象。
2.9.2 HQL查询
- 该查询要求实体类中具有相同投影作为参数的带参构造器。所以,首先要在Student类中添加相应的带参构造器。
public Student(String name, int age) { super(); this.name = name; this.age = age; }
//使用HQL查询所有学生的姓名与年龄 @Test public void test09_HQL() { try { session.beginTransaction(); String hql = "select new Student(name, age) from Student"; List
students = session.createQuery(hql).list(); for(Student student: students) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.10 分组查询
2.10.1 SQL查询
- having,对分组结果进行筛选。下面代码实现的功能是,查询出所有年龄段,以及人数多于1人的年龄段。
//查询出所有的年龄段,以及人数大于1人的年龄段 @Test public void test10_SQL() { try { session.beginTransaction(); String sql = "select sage from t_student group by sage"; List
2.9.2 HQL查询
//查询出所有的年龄段,以及人数大于1人的年龄段 @Test public void test10_HQL() { try { session.beginTransaction(); String hql1 = "select age from Student group by age"; List
result1 = session.createQuery(hql1).list(); for(Object object: result1) { System.out.println(object); } System.out.println("------------------"); String hql2 = "select age from Student group by age having count(age) > ?"; List result2 = session.createQuery(hql2).setInteger(0, 1).list(); for(Object object: result2) { System.out.println(object); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.9.3 QBC查询
-
Hibernate不直接支持having操作。
//查询出所有的年龄段 @Test public void test10_QBC() { try { session.beginTransaction(); List
result1 = session.createCriteria(Student.class). setProjection(Projections.groupProperty("age")).list(); for(Object object: result1) { System.out.println(object); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.11 Query的list()与iterator()
2.11.1 Query的list()查询
//使用Query的list()查询所有 @Test public void test11_HQL_list() { try { session.beginTransaction(); String hql = "from Student"; //第一次查询 List
students1 = session.createQuery(hql).list(); for(Student student : students1) { System.out.println(student); } //第二次查询 List students2 = session.createQuery(hql).list(); for(Student student : students2) { System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.10.2 Query的iterate()查询
//使用Query的iterate()查询所有 @Test public void test11_HQL_list_iterate() { try { session.beginTransaction(); String hql = "from Student"; //第一次查询 Iterator
it1 = session.createQuery(hql).iterate(); while(it1.hasNext()) { Student student = it1.next(); System.out.println(student); } //第二次查询 Iterator it2 = session.createQuery(hql).iterate(); while(it2.hasNext()) { Student student = it2.next(); System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.10.3 Query的list()与iterator()的区别
- 使用Query接口的list()和iterate()进行查询,查看其控制台的SQL语句的输出情况,可以看到它们区别主要有两点:
1、使用list(),会一次性将所有符合条件的记录查询出来,而使用iterate(),则首先会查询所有符合条件的记录的id,然后在根据这些id逐个查询出记录的具体内容。
2、使用list(),不会使用缓存机制,即每次执行一次查询代码,控制台会执行一次SQL查询语句;而使用iterate(),则会使用缓存机制,只有第一次会执行SQL查询,再往后的查询会直接从缓存中读取。2.10.4 N + 1问题
- 使用Query的iterate()方法虽然使用了Hibernate的缓存机制,但是同时也出现了N+1问题。
- 所谓N+1问题是指,从查询出有效数据角度来说,若要查询符合某条件的对象时,使用list(),则一次性即可查询出所有。而若使用iterate(),则会先查询出所有满足条件的对象的id,然后再逐个id进行select查询,即需要经过N+1次才能得出有效结果。这就是N+1问题。
2.10.5 N + 1问题的避免
-
若想要使用Hibernate缓存,即使用iterate()方法,还想要避免N + 1问题,就要保证在执行iterate()时,缓存中已经有数据,这样既可利用缓存,有可以避免N+1问题。所以,可以在第一次查询时使用list(),而以后的查询则使用iterate()。
//N + 1问题的避免 @Test public void test11_HQL_list_iterate() { try { session.beginTransaction(); String hql = "from Student"; //第一次查询 List
students = session.createQuery(hql).list(); for(Student student : students) { System.out.println(student); } //第二次查询 Iterator it2 = session.createQuery(hql).iterate(); while(it2.hasNext()) { Student student = it2.next(); System.out.println(student); } session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } } 2.12 命名查询namedQuery
- 所谓namedQuery,是指HQL语句不直接写在Java代码中,而写入到映射文件。Java代码从映射文件中将被命名的HQL,通过名称读出执行。
- 将HQL写入到配置文件中的好处是,在项目真正上线后,若只需要修改HQL就可进行检索优化,则需修改配置文件中的HQL后,重启服务器即可,无需再次编译项目。
- 在映射文件中通过
通过命名定义HQL,该标签写到哪个映射文件均可。 from Student where id = ? -
java代码中通过Sesion的getNamedQuery()方法可以将映射文件中指定的HQL读取出来。
//命名查询 @Test public void test12() { try { session.beginTransaction(); Student student = (Student) session.getNamedQuery("queryById").setInteger(0, 5).uniqueResult(); System.out.println(student); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } }