【Hibernate 6】常用的hql语句以及N+1问题

HQL:Hibernate Query Language,是Hibernate框架中的查询语言,十分接近于SQL语言!以下介绍一些常用的Hql语句:

一、测试类

Classes类:

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.bjpowernode.hibernate;

import java.util.Set;

public class Classes {
	
	private int id;
	
	private String name;
	
	private Set students;

	......
	
}</span>


Student类:

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.bjpowernode.hibernate;

import java.util.Date;

public class Student {

	private int id;
	
	private String name;

	private Date createTime;
	
	private Classes classes;
	
	public Student() {
	}

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	......
}</span>


初始化数据类:

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.bjpowernode.hibernate;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.hibernate.Session;

public class InitData {

	public static void main(String[] args) {
			Session session = HibernateUtils.getSession();

			try {
				session.beginTransaction();

				for(int i=0; i<10; i++){
				
					Classes classes = new Classes();
					classes.setName("班级"+i);
					session.save(classes);
					
					for(int j=0; j<10; j++){
						Student student = new Student();
						student.setName("班级"+i+"的学生"+j);
						student.setCreateTime(randomDate("2009-07-01","2009-09-01"));
						
						//在内存中建立由student指向classes的引用
						student.setClasses(classes);
						session.save(student);
					}
				}
				
				for(int i=0; i<5; i++){
					Classes classes = new Classes();
					classes.setName("无学生班级"+i);
					session.save(classes);
				}
				
				for(int i=0; i<10; i++){
					Student student = new Student();
					student.setName("无业游民"+i);
					session.save(student);
				}
				
				session.getTransaction().commit();
			} catch (Exception e) {
				e.printStackTrace();
				session.getTransaction().rollback();
			} finally{
				HibernateUtils.closeSession(session);
			}
		}	
		
		/**
		 * 获取随机日期
		 * @param beginDate 起始日期,格式为:yyyy-MM-dd
		 * @param endDate 结束日期,格式为:yyyy-MM-dd
		 * @return
		 */
		private static Date randomDate(String beginDate,String endDate){
			try {
				SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
				Date start = format.parse(beginDate);
				Date end = format.parse(endDate);
				
				if(start.getTime() >= end.getTime()){
					return null;
				}
				
				long date = random(start.getTime(),end.getTime());
				
				return new Date(date);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}
		
		private static long random(long begin,long end){
			long rtn = begin + (long)(Math.random() * (end - begin));
			if(rtn == begin || rtn == end){
				return random(begin,end);
			}
			return rtn;
		}
}</span>

 

 

 

二、常用的hql语句

2.1,简单属性查询

单一属性:

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//返回结果集属性列表,元素类型和实体类中的属性类型一致
List students = session.createQuery("select name from Student").list();
for (Iterator iter=students.iterator(); iter.hasNext();) {
	String name = (String)iter.next();
	System.out.println(name);
}</span>

多个属性:

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//查询多个属性,返回对象数组集合,数组元素的类型与查询的属性类型一致
//数组的长度与select中查询的属性个数一致
List students = session.createQuery("select id, name from Student").list();
for (Iterator iter=students.iterator(); iter.hasNext();) {
	Object[] obj = (Object[])iter.next();
	System.out.println(obj[0] + ", " + obj[1]);
}</span>

多个属性查询,返回实体对象

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//可以使用hql返回Student对象
<span style="color:#ff6666;">//需要提供构造函数</span>
List students = session.createQuery("select new Student(id, name) from Student").list();
for (Iterator iter=students.iterator(); iter.hasNext();) {
	Student student = (Student)iter.next();
	System.out.println(student.getId() + ", " + student.getName());
}</span>

 

 

 

2.2,带参数的查询

 

拼串查询

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">List students = session.createQuery("select s.id, s.name from Student s where s.name like '%0%'").list();</span>

使用?方式传递参数,索引从0开始,值能使用单引号引起来

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//方法链编程,建议采用此种方式
List students = session.createQuery("select s.id, s.name from Student s where s.name like ?")
		.setParameter(0, "%0%")
		.list();</span>

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//采用 ?方式,查询学号为1,2,3,4,5的学生
List students = session.createQuery("select s.id, s.name from Student s where s.id in(?, ?, ?, ?, ?)")
		.setParameter(0, 1)
		.setParameter(1, 2)
		.setParameter(2, 3)
		.setParameter(3, 4)
		.setParameter(4, 5)
		.list();</span>

 

 

使用 :参数名的方式传递参数

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">//可以采用 :参数名 的方式传递参数
List students = session.createQuery("select s.id, s.name from Student s where s.name like :myname")
		.setParameter("myname",  "%0%")
		.list();</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;">//采用 :参数名 方式,查询学号为1,2,3,4,5的学生
List students = session.createQuery("select s.id, s.name from Student s where s.id in(:ids)")
		.setParameterList("ids", new Object[]{1, 2, 3, 4, 5})
		.list();</span>

 

2.3,对象导航查询(通过cross join查询,慎重考虑使用)

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">List students = session.createQuery("from Student s where s.classes.name like '%2%'")
		.list();</span>

 

2.4,外置命名查询

 

实体映射文件中的配置:

 

<span style="font-family:KaiTi_GB2312;font-size:18px;"><query name="queryStudent">
	<![CDATA[
		select s from Student s where s.id <?
	]]>
</query>	</span>

查询时的调用:

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">List students = session.getNamedQuery("queryStudent")
		.setParameter(0, 10)
		.list();</span>

 

2.5,分页查询

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">List students = session.createQuery("from Student")
		.setFirstResult(1)
		.setMaxResults(2)
		.list();</span>

setFirstResult:表示第一条记录的索引;setMaxResults:表示每页显示的最大记录数

 

2.6,原生sql语句查询

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">List students = session.createSQLQuery("select * from t_student").list();</span>

 

2.7,统计查询(实际上是调用函数的查询)

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">String hql = "select c.name, count(s) from Classes c join c.students s group by c.name order by c.name";
List students = session.createQuery(hql).list();</span>

 

2.8,过滤查询

 

实体对象映射文件配置:

 

<pre name="code" class="html"><span style="font-family:KaiTi_GB2312;font-size:18px;">	<class name="com.bjpowernode.hibernate.Student" table="t_student">
		<id name="id">
			<generator class="native"/>
		</id>
		<property name="name"/>
		<property name="createTime"/>
		<many-to-one name="classes" column="classesid"/>
		<span style="color:#ff6666;"><filter name="testFilter" condition="id < :myid"></filter></span>
	</class>
	
	<span style="color:#ff6666;"><filter-def name="testFilter">
		<filter-param type="integer" name="myid"/>
	</filter-def></span></span>

 

 

 

 

 

 

<span style="font-family:KaiTi_GB2312;font-size:18px;">session.enableFilter("testFilter")
	.setParameter("myid", 10);
List students = session.createQuery("from Student").list();</span>

 


以上就是一些常用的hql语句,下面介绍一下Hibernate中的N+1问题

 

 

三、N+1问题

所谓的N+1问题实际上是指Hibernate在查询的时候,发出了N+1条sql语句:

1:发出一条查询id列表的语句

N:根据id发出N条sql语句,加载相关对象

Iterator接收查询结果:

 

<pre name="code" class="java"><span style="font-family:KaiTi_GB2312;font-size:18px;">Iterator iter = session.createQuery("from Student").iterate();
while (iter.hasNext()) {
	Student student = (Student)iter.next();
	System.out.println(student.getName());
}</span>

 

 


控制台打印结果:

 


解决方案:

每次执行list操作后会将数据放到session的缓存中(一级缓存),所以采用iterate的时候, 首先会发出一条查询id列表的语句,再根据id到缓存中加载相应的数据,如果缓存中存在与之匹配的数据,则不再发出根据id查询的sql语句,直接使用缓存中的数据。

所以,使用list去接收查询的语句,可避免N+1的问题。但如果缓存中存在数据,使用Iterate会比使用List性能高!

注意: 在默认情况下,每次执行list查询实体对象都会发出查询语句,除非配置了查询缓存。虽然一级缓存中存在Student数据,但list不用,所以仍然发出查询语句, 其实list就是只向缓存中放入数据,而不利用缓存中的数据

 

四、总结

以上就是使用Hibernate框架在查询中需要注意的一些问题。在谈到N+1问题的时候,说到了缓存。那么缓存是提高Hibernate整个框架性能的一个重要因素,下篇博客谈谈Hibernate的缓存机制。它的一级缓存、二级缓存等的开启和管理!
 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值