一、HQL基本概念
HQL是Hibernate Query Language的简称,它是面向对象的查询语句,完整的HQL语句形式如下: select…… from …… where …… group by …… having …… order by …… asc/desc,其中select子句是用来指定查询结果中的对象和属性,并指定以何种数据类型返回;from子句用来指定HQL语句的查询目标,即映射配置的持久化类及其属性;where子句是用来设置查询条件,限制返回结果和范围的;group by子句用来进行分组;having子句用来对分组进行条件限制;order by子句是用来对查询到的结果进行排序的。从语句的格式来看,HQL与SQL语句是类似的,但是HQL与SQL有本质的不同,HQL是面向对象的查询语言,而SQL是面向数据库的;另一点不同的是,HQL语句最简单只需要一个from子句就可以了,其他的都可以省略,而SQL是不行的。
刚开始使用HQL时特别需要注意的是HQL因为是面向对象的,所以使用HQL时对Java类和属性是大小写敏感的,这与SQL有本质的不同,SQL语句对表格和列都不是大小写敏感的,但是在HQL中,HQL的关键字是不区分大小写的,例如查询子句的select,既可以是大写也可以是小写。
在Hibernate中要使用HQL,需要使用Query接口,通过这个接口解析HQL语句,然后根据配置信息,将HQL转换为对应的SQL,接着去数据库执行相应的查询操作。而使用Query接口的过程是这样的:首先使用Session的createQuery(String hql)方法创建一个Query实例,将相应的hql语句作为参数传进这个方法;接着使用Query的list()方法执行HQL查询,list()方法返回结果数据类型为java.util.List,List集合中存放符合查询条件的持久化对象。
下面我们就通过实例来详细讲解一下HQL的使用。
二、使用前准备
在讲解HQL之前,需要在数据库中创建相应的表并且配置相应的实体类和映射文件,在这个例子中我们创建两张表,商家表seller和商品表commodity,其中商品表通过外键与商家表关联,一个商家可能对应多个商品。建表语句如下,首先是商家表seller:
CREATE TABLE `seller` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`tel` varchar(1000) DEFAULT NULL,
`address` varchar(2000) DEFAULT NULL,
`website` varchar(500) DEFAULT NULL,
`star` int(11) DEFAULT NULL,
`business` varchar(2000) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
接着是商品表commodity:
CREATE TABLE `commodity` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`price` double(11,2) DEFAULT NULL,
`unit` varchar(50) DEFAULT NULL,
`category` varchar(100) DEFAULT NULL,
`description` varchar(1000) DEFAULT NULL,
`seller` int(11) DEFAULT NULL,
PRIMARY KEY (`Id`),
KEY `fk_commodity_1_idx` (`seller`),
CONSTRAINT `fk_commodity_1` FOREIGN KEY (`seller`) REFERENCES `seller` (`Id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
在表中插入一些测试数据,seller表:
commodity表:
然后创建两个实体类,Seller和Commodity。
package com.imooc.vo;
public class Seller {
private int id;
private String name;
private String tel;
private String address;
private String website;
private int star;
private String business;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public int getStar() {
return star;
}
public void setStar(int star) {
this.star = star;
}
public String getBusiness() {
return business;
}
public void setBusiness(String business) {
this.business = business;
}
@Override
public String toString() {
return "Seller [id=" + id + ", name=" + name + ", tel=" + tel
+ ", address=" + address + ", website=" + website + ", star="
+ star + ", business=" + business + "]";
}
public Seller() {
}
}
package com.imooc.vo;
public class Commodity {
private int id;
private String name;
private double price;
private String unit;
private String category;
private String description;
private Seller seller;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Seller getSeller() {
return seller;
}
public void setSeller(Seller seller) {
this.seller = seller;
}
@Override
public String toString() {
return "Commodity [id=" + id + ", name=" + name + ", price=" + price
+ ", unit=" + unit + ", category=" + category
+ ", description=" + description + ", seller=" + seller.getId() + "]";
}
}
接着就是配置这两个实体类的映射文件,Seller.hbm.xml和Commodity.hbm.xml。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-26 20:39:32 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.vo.Seller" table="seller">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="name" />
</property>
<property name="tel" type="java.lang.String">
<column name="tel" />
</property>
<property name="address" type="java.lang.String">
<column name="address" />
</property>
<property name="website" type="java.lang.String">
<column name="website" />
</property>
<property name="star" type="int">
<column name="star" />
</property>
<property name="business" type="java.lang.String">
<column name="business" />
</property>
</class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-26 20:39:32 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.vo.Commodity" table="commodity">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="name" />
</property>
<property name="price" type="double">
<column name="price" />
</property>
<property name="unit" type="java.lang.String">
<column name="unit" />
</property>
<property name="category" type="java.lang.String">
<column name="category" />
</property>
<property name="description" type="java.lang.String">
<column name="description" />
</property>
<many-to-one name="seller" class="com.imooc.vo.Seller" >
<column name="seller" />
</many-to-one>
</class>
</hibernate-mapping>
最后需要在hibernate.cfg.xml文件中配置这两个映射文件,这个步骤在这里就不再赘述了,之前的几篇笔记有介绍过很多次了。到现在我们已经完成了所有的准备工作,接下来就可以具体来分析HQL的使用了。
三、HQL的使用
1、from子句
from子句是HQL语句的最简形式,from子句指定了HQL的查询主体,与SQL的from子句指定查询某一张表不同的是HQL的from子句指定的是Java的持久化类及其属性,所以在from子句后面跟的并不是某一个表名,而是一个Java实体类名,我们来看例子,从commodity表中查询所有的商品信息,然后根据商品信息查询出对应的商家信息。我们使用JUnit来进行验证,其中SessionFactory、Session和Transaction的初始化与销毁在这里也不赘述了,与前面几篇文章里介绍的一致。 @Test
public void testQuery() {
String hql = "from Commodity";
Query query = session.createQuery(hql);
List<Commodity> commoditys = query.list();
for(Commodity commodity : commoditys) {
System.out.println(commodity);
Seller seller = commodity.getSeller();
System.out.println(seller);
}
}
在这个例子里我们直接使用from子句作为HQL的查询子句,这是因为from子句是HQL的最简形式,我们将HQL语句作为参数传入createQuery()中创建Query实例,然后使用Query的list()方法就可以查询出所有的商品信息了,因为商品实体类中配置了与商家的多对一关系映射,所以查询到商品信息之后,就可以直接获取到商家信息了。在这个例子里我们写的HQL语句中,from子句后面没有引入Commodity类的全限定名,而是直接引入类名Commodity,当然我们也可以写全限定名,但是因为Hibernate会通过映射,自动导入缺省的全限定名。,所以写全限定名就有些多余了。
在HQL中也可以使用像SQL一样的别名,我们可以为被查询的类指定别名,在HQL语句的其他部分我们就可以通过别名引用该类了,例如上面这个例子里的HQL语句,我们可以写成这样:
String hql = "from Commodity as c";
因为这个例子只是简单地查询所有的数据,所以别名用处并不大,但是在更复杂的查询中别名的使用会有很大的用处,当然使用别名时要起容易辨识的别名,这样才能更方便地使用别名。
2、select子句
select子句是用来指定查询结果的对象和属性的,我们可以在select子句中选择返回什么类型的对象。
(1)当select子句中未指定返回的数据类型的时候,默认返回Object[]的一个List,我们看下面的例子。
@Test
public void testSelectClauseObjectArray() {
String hql = "select s.name,s.tel,s.address from Seller s";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for(Object[] objs : list) {
System.out.println("name=" + objs[0] + ",tel=" + objs[1] + ",address=" + objs[2]);
}
}
这个例子中我们需要获取name、tel和address信息,但是没有指定返回的类型,所以在调用Query.list()方法时,默认返回Object的数组类型,然后通过数组的下标获得每一个属性的值。这里需要注意的是,如果select子句只返回一个属性类型时,那么返回的类型是Object类型 而不是Object[],如果定义为Object[],系统将会报错。
@Test
public void testSelectClauseObjectArray() {
String hql = "select s.name,s.tel,s.address from Seller s";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for(Object[] objs : list) {
System.out.println("name=" + objs[0] + ",tel=" + objs[1] + ",address=" + objs[2]);
}
hql = "select s.name from Seller s";
query = session.createQuery(hql);
List<Object> list2 = query.list();
for(Object obj : list2) {
System.out.println("name=" + obj);
}
}
(2)我们也可以在select子句中指定返回类型为一个List的集合,这样我们就可以通过List的方法获得对象和属性了。
@Test
public void testSelectClauseList() {
String hql = "select new list(s.name, s.tel, s.address) from Seller s";
Query query = session.createQuery(hql);
List<List> list = query.list();
for(List l : list) {
System.out.println("name=" + l.get(0) + ",tel=" + l.get(1) + ",address=" + l.get(2));
}
}
在这个例子中我们在select子句里使用new list(属性1,属性2,……)的方式定义了返回类型为一个List,之后我们在Query.list()方法里获得返回类型为一个List的集合,然后我们就可以通过List的get()方法获得相应的属性了。
(3)我们也可以在select子句中指定返回类型为一个Map集合,但要注意的是该Map的key为索引值,并且为字符串类型。
@Test
public void testSelectClauseMap() {
String hql = "select new map(s.name, s.tel, s.address) from Seller s";
Query query = session.createQuery(hql);
List<Map> list = query.list();
for(Map l : list) {
System.out.println("name=" + l.get("0") + ",tel=" + l.get("1") + ",address=" + l.get("2"));
}
}
我们在select子句中使用new map(属性1,属性2,……)来指定返回一个Map集合,同时使用Map.get("0")的方式获得Map集合相应的value值,这里的0、1、2等是要获得的属性的索引,但是与Object[]和List不同的是,这里的索引是字符串类型的,当然我们也可以通过为属性指定别名,然后以该别名作为key值来获得相应的value值,这种方式更加清晰,可读性比较好。
@Test
public void testSelectClauseMap2() {
String hql = "select new map(s.name as name, s.tel as tel, s.address as address) from Seller s";
Query query = session.createQuery(hql);
List<Map> list = query.list();
for(Map m : list) {
System.out.println("name=" + m.get("name") + ",tel=" + m.get("tel") + ",address=" + m.get("address"));
}
}
(4)我们也可以使用自定义类型返回,首先我们需要在持久化类中定义对应的构造方法,构造方法的参数需要和查询字段一致,然后在select子句中调用定义的构造方法。例如上面的例子中我们都是获取Seller的name、tel和address属性,那么我们可以在Seller类中定义相应的构造方法。
public Seller(String name, String tel, String address) {
this.name = name;
this.tel = tel;
this.address = address;
}
然后在select子句中使用相应的构造方法,这样就可以查询到相应属性的值了。
@Test
public void testSelectClauseSelf() {
String hql = "select new Seller(s.name, s.tel, s.address) from Seller s";
Query query = session.createQuery(hql);
List<Seller> list = query.list();
for(Seller seller : list) {
System.out.println("name=" + seller.getName() + ",tel=" + seller.getTel() + ",address=" + seller.getAddress());
}
}
需要注意的是,增加自定义构造方法时,一定要补充默认构造方法,否则hql=" from classname" 会出错,因为在没有指定的查询的返回集合时候,Hibernate会自动去找默认构造方法。
(5)我们也可以使用distinct关键字来过滤重复的查询结果,distinct关键字的用法和SQL的distinct关键字用法相同。
@Test
public void testSelectDistinct() {
String hql = "select distinct s.star from Seller s";
Query query = session.createQuery(hql);
List<Object> list = query.list();
for(Object o : list) {
System.out.println(o);
}
}
3、where子句
where子句是用来设置查询条件,限制返回的查询结果的,在HQL中where子句的用法与SQL类似,在这里简单介绍一下。
(1)、比较运算,常用的比较运算就是=、<>、>、<、>=和<=,例如下面这个例子就是获得价格大于400的所有产品信息。
@Test
public void testWhere1() {
String hql = "from Commodity c where c.price > 400";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
在SQL中我们使用is [not] null来判断值是否为空,在HQL中我们可以直接使用=null或者<>null来进行判断,当然也可以使用SQL的is [not] null的形式来判断。
@Test
public void testWhere2() {
String hql = "from Commodity c where c.description <> null";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
(2)、范围运算,我们使用[not] in(列表)和[not] between 值1 and 值2的方式来表示范围,其中[not] in(列表)表示是否在一系列的值列表中,而[not] between 值1 and 值2表示值是否在一个范围内。
@Test
public void testWhere3() {
String hql = "from Commodity c where c.seller not in(1,3,4)";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
@Test
public void testWhere4() {
String hql = "from Commodity c where c.seller between 1 and 3";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
(3)、字符串模式匹配,我们可以使用like关键字来进行字符串的模式匹配,使用通配符%和_来进行匹配,%可以匹配任意多个字符,而_只匹配一个字符。
@Test
public void testWhere5() {
String hql = "from Commodity c where c.category like '电_'";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
@Test
public void testWhere6() {
String hql = "from Commodity c where c.category like '电%'";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
(4)、逻辑运算,在前面我们已经涉及到了逻辑非运算not,我们还有逻辑与and和逻辑或or运算。
@Test
public void testWhere7() {
String hql = "from Commodity c where c.price>100 and c.price<5000 and c.category like '电%'";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
这个例子中我们查找价格在100到5000之间,并且类型以电字开头的所有商品信息。
(5)、集合运算,在HQL中还有集合运算的概念,其中is [not] empty表示集合[不]为空,不包含任何元素,类似于SQL中的exists关键字,member of表示元素属于集合,类似于in。
(6)、四则运算,在HQL中,我们可以使用+、-、×、/四则运算,四则运算既可以在where子句中使用,也可以在select子句中使用。
@Test
public void testWhere8() {
String hql = "from Commodity c where c.price * 5 > 3000";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
(7)、查询单个对象,我们可以使用Query.uniqueResult()表示查询单个对象,也就是说uniqueResult()方法返回的只是单个的对象,如果返回结果有多个,那么这个方法就会抛出异常,我们看下面这个例子。
@Test
public void testWhere9() {
String hql = "from Commodity c where c.name='中式童装'";
//String hql = "from Commodity c where c.price > 100";
Query query = session.createQuery(hql);
Commodity c = (Commodity) query.uniqueResult();
System.out.println(c);
}
如果我们使用商品名称为中式童装来进行查询,那么最后获取到的结果就只有一个,可以使用uniqueResult(),如果我们使用商品价格大于100作为查询条件,那么会查询到多个结果,这时再使用uniqueResult()方法时就会抛出异常了。所以,在使用uniqueResult()方法时我们必须要通过where子句将查询得到的记录只限制为一条或者查询不到。
4、order by子句
类似于SQL,我们使用order by来对查询的结果进行排序,asc为升序,desc为降序,默认为升序,多个排序规则用“,”隔开,表示前一个规则中排序条件相同则用后一个排序规则。
@Test
public void testOrderBy() {
String hql = "from Commodity c order by seller.id asc,price desc";
Query query = session.createQuery(hql);
List<Commodity> list = query.list();
for(Commodity c : list) {
System.out.println(c);
}
}
在这个例子中我们查询商品信息,然后对查询到的商品信息先按照卖家id进行升序排序,如果卖家id相同则按照商品价格降序排序。