Java的Hibernate框架中用于操作數(shù)據(jù)庫(kù)的HQL語(yǔ)句講解
上次我們一起學(xué)習(xí)了用Criteria進(jìn)行相關(guān)的操作,但由于Criteria并不是Hibernate官方推薦的查詢方式,我們也并不多用?,F(xiàn)在我們來(lái)看一下官方推薦的HQL,一起學(xué)習(xí)一下它的強(qiáng)大。
說(shuō)是HQL,也就是Hibernate查詢語(yǔ)句,和SQL有什么區(qū)別呢?一個(gè)字母的區(qū)別,哈哈。
當(dāng)然不是這樣,HQL和SQL的區(qū)別在于思想的不同,HQL是用面向?qū)ο蟮姆较蜻M(jìn)行查詢,而SQL則是對(duì)數(shù)據(jù)庫(kù)二維表進(jìn)行查詢,這里包含的是思想的不同。HQL實(shí)際上也是SQL,它由Hibernate幫我們?cè)趦?nèi)部進(jìn)行轉(zhuǎn)換,生成SQL。
1)廢話不多說(shuō),我們直接看一下它的強(qiáng)大。
from User
這個(gè)代碼很熟悉吧,因?yàn)槲覀冊(cè)赟QL中經(jīng)常也用到from 表名,但這里有點(diǎn)不同的是User在這里并不是表名,而是實(shí)體類的名稱,由hibernate幫我們進(jìn)行映射。
聯(lián)想SQL語(yǔ)句,如果我們想查出某個(gè)屬性,并且根據(jù)某個(gè)屬性進(jìn)行條件限制,很簡(jiǎn)單可以得到類似語(yǔ)句:
select usr.name,usr.age from User where usr.age > 20 and usr.age < 60
這樣我們就查出了年齡大于20且小于60的User的姓名和年齡。很容易理解。
SQL語(yǔ)句中的and,or,like,<,>,=等都可以在HQL中進(jìn)行使用。
需要注意的是當(dāng)我們查詢多個(gè)屬性時(shí),返回的結(jié)果是一個(gè)Object[]數(shù)組,而只有單個(gè)時(shí)是返回Object,這個(gè)需要不同的解析方式,所以在查詢時(shí)需要注意。
2)當(dāng)然,我們前面說(shuō)了HQL是面向?qū)ο蟮模覀冞@樣做,就不是面向?qū)ο蟮乃枷肓?。我們?lái)改一下:
select new User(usr.name,usr.age) from User usr where usr.age > 20
Unable to locate appropriate constructor on class [org.hibernate.tutorial.domain8.User]
它找不到合適的構(gòu)造函數(shù)。很明白,加上接收對(duì)應(yīng)參數(shù)的構(gòu)造函數(shù)就可以了。
注意,上面當(dāng)我們進(jìn)行查出的時(shí)候并沒(méi)有查出相應(yīng)的ID,如果此時(shí)我們調(diào)用saveOrUpdate方法時(shí),它實(shí)際上執(zhí)行的是保存的操作。
我們看一下測(cè)試代碼:
我在執(zhí)行完上面的查詢語(yǔ)句后,進(jìn)行下面的操作:
while(iter.hasNext()) {
User user = (User)iter.next();
user.setName("sun2");
session.saveOrUpdate(user);
}
這時(shí)Hibernate的語(yǔ)句為:
Hibernate: insert into USER (USER_NAME, age) values (?, ?)
它新插入一條,而不是更新。
那么如果我們需要它進(jìn)行更新的時(shí)候就需要把ID一起查出:
select new User(usr.name,usr.age,usr.id) from User usr where usr.age > (select avg(usr.age) from usr)
記得修改User構(gòu)造方法。
這時(shí)我們?cè)賵?zhí)行我們的測(cè)試代碼,此時(shí)會(huì)得到:
Hibernate: update USER set USER_NAME=?, age=? where USER_ID=?
3)我們可以在HQL語(yǔ)句中加上SQL函數(shù):
select usr.name from User usr where usr.age > (select avg(usr.age) from usr)
這段HQL查出年齡大于平均年齡的User的name。
4)在Hibernate 3中我們可以很方便地更新和刪除對(duì)象,而不必像2中需要先load然后再delete,我們可以直接一條語(yǔ)句搞定:
update User set name='123123' where name='sun33'
刪除語(yǔ)句類似:
delete User where name='123123'
5)Hibernate中也可以方便地進(jìn)行分組和排序,只要運(yùn)用group by 和 order by 即可,這時(shí)不多講了。
6)我們看到上面都是直接把值寫(xiě)入進(jìn)行查詢或更新的,如果我們需要?jiǎng)討B(tài)賦值,或賦值的太多,總不能跟JDBC一樣用字符串拼接吧,估計(jì)超過(guò)5個(gè),項(xiàng)目組的人都想罵娘了,呵呵。
還是用著現(xiàn)代化的方法,用占位符來(lái)代替然后再設(shè)置具體值。
我們直接代碼:
Query query = session.createQuery("select new User(usr.name,usr.age,usr.id) from User usr where usr.name=?");
query.setString(0,"shun");
我們看到這種方法跟我們直接用的PreparedStatement類似,都是通過(guò)set***進(jìn)行設(shè)值的,但不同的是,這里的position從0開(kāi)始,而PreparedStatement從1開(kāi)始,這里要特別注意。
Hibernate2中還有session.find這種方法的,但由于現(xiàn)在用的是3并不多說(shuō)它了。
上面我們用的這種占位符叫順序占位符,另外有一種叫引用占位符的,我們來(lái)看一下:
Query query = session.createQuery("select new User(usr.name,usr.age,usr.id) from User usr where usr.name=:name");
query.setParameter("name","shun");
看到我們HQL語(yǔ)句當(dāng)中有一個(gè):name這樣的東西,這個(gè)就是引用占位符,我們只需要在后面通過(guò)setParameter進(jìn)行設(shè)值即可,注意這里的第一個(gè)參數(shù)需要對(duì)應(yīng)HQL語(yǔ)句中的占位符的值。
當(dāng)然,也許有人又會(huì)說(shuō),這個(gè)不面向?qū)ο?,那么我們就又?lái)面向?qū)ο笠话眩?br />
首先弄一個(gè)類來(lái)封裝我們查詢的值
public class UserQuery {
private String name;
private int age;
//省略Get/Set方法
}
Query query = session.createQuery("select new User(usr.name,usr.age,usr.id) from User usr where usr.name=:name");
UserQuery uq = new UserQuery();
uq.setName("shun");
query.setProperties(uq);
我們?cè)诖a從直接通過(guò)此類進(jìn)行封裝我們需要查詢的值。很面向?qū)ο蟀伞?br />
有些項(xiàng)目組有一些奇怪的規(guī)定,不許在代碼中出現(xiàn)SQL語(yǔ)句,如果這是一個(gè)規(guī)范,那我見(jiàn)過(guò)的我們公司的代碼,全部都是不合格的,杯具的一大堆字符串拼接,看著就郁悶啊。維護(hù)現(xiàn)有項(xiàng)目的人真是傷不起啊。
代碼中不允許出現(xiàn)SQL語(yǔ)句,這是建議是不錯(cuò),但還是要看場(chǎng)合。我們來(lái)看一下Hibernate怎么把HQL配置在映射文件中。
直接看配置文件:
<query name="queryByName"> <![CDATA[ from User usr where usr.name=:name ]]> </query>
我們添加了一個(gè)這樣的標(biāo)簽,它表明里面是HQL語(yǔ)句。
當(dāng)我們需要取到這個(gè)語(yǔ)句時(shí),也只需要在代碼中加入一句:
Query query = session.getNamedQuery("queryByName");
這樣也就取到了這個(gè)HQL語(yǔ)句。
HQL也可以用SQL中的組合查詢,比如inner join,left outer join,right outer join,full join。
下面我們來(lái)看一下它們的用法:
還是先看一下實(shí)體類,我們測(cè)試中要用到的:
public class TUser implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private int age;
private String name;
private Set<Address> addresses = new HashSet<Address>();
//省略Get/Set方法
}
public class Address implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private String address;
private TUser user;
//省略Get/Set方法
}
下面我們看一下映射文件:
<hibernate-mapping package="org.hibernate.tutorial.domain6"> <class name="TUser" table="t_user" dynamic-insert="true" dynamic-update="true"> <id name="id" column="id"> <generator class="native" /> </id> <property name="name" type="java.lang.String" column="name"/> <property name="age" type="java.lang.Integer" column="age"/> <set name="addresses" cascade="all" table="t_address" inverse="true"> <key column="user_id" /> <one-to-many class="Address"/> </set> </class> </hibernate-mapping> <hibernate-mapping package="org.hibernate.tutorial.domain6"> <class name="Address" table="t_address" dynamic-insert="false" dynamic-update="false"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <property name="address" column="address" type="java.lang.String" /> <many-to-one name="user" class="TUser" column="user_id" not-null="true"></many-to-one> </class> </hibernate-mapping>
大家只要做一下相應(yīng)的包名修改就可以了。
下面我們正式進(jìn)行測(cè)試:
在測(cè)試前我們看一下表中的數(shù)據(jù):
t_address表數(shù)據(jù)如下:

t_user表數(shù)據(jù)如下:

1)首先我們看一下inner join,它在HQL中由inner join fetch,注意這里fetch的意思是指把需要的數(shù)據(jù)取出來(lái),如果不用fetch,我們?nèi)〕鰜?lái)的數(shù)據(jù)是Object[]數(shù)據(jù)類型的。
我們先看一下
from TUser usr inner join fetch usr.addresses
當(dāng)我們運(yùn)行它時(shí),我們看到hibernate輸出為:
Hibernate: select tuser0_.id as id1_0_, addresses1_.id as id0_1_, tuser0_.name as name1_0_, tuser0_.age as age1_0_, addresses1_.address as address0_1_, addresses1_.user_id as user3_0_1_, addresses1_.user_id as user3_0__, addresses1_.id as id0__ from t_user tuser0_ inner join t_address addresses1_ on tuser0_.id=addresses1_.user_id
我們?cè)趍ysql中運(yùn)行可以看到結(jié)果:

我們可以看到hibernate將它轉(zhuǎn)換成inner join語(yǔ)句,并查出address。
我們看到結(jié)果中并沒(méi)有shun4這個(gè)記錄,因?yàn)樗](méi)有相應(yīng)的address與它記錄。
而我們用inner join而不要fetch時(shí),它打印的語(yǔ)句為:
Hibernate: select tuser0_.id as id1_0_, addresses1_.id as id0_1_, tuser0_.name as name1_0_, tuser0_.age as age1_0_, addresses1_.address as address0_1_, addresses1_.user_id as user3_0_1_ from t_user tuser0_ inner join t_address addresses1_ on tuser0_.id=addresses1_.user_id
似乎語(yǔ)句沒(méi)什么區(qū)別,但是當(dāng)我們查出來(lái)后它得到的是Object[]數(shù)組類型的,這個(gè)解析的時(shí)候需注意。
當(dāng)我們不用fetch,而只是inner join時(shí),我們需要這樣來(lái)解析:
Query query = session.createQuery("from TUser usr inner join usr.addresses");
List list = query.list();
Iterator iter = list.iterator();
while(iter.hasNext()) {
Object[] results = (Object[])iter.next();
for (int i = 0; i < results.length; i ++ ) {
System.out.println(results[i]);
}
}
我們看到打印的結(jié)果:
org.hibernate.tutorial.domain6.TUser@16925b0 org.hibernate.tutorial.domain6.Address@914f6a org.hibernate.tutorial.domain6.TUser@787d6a org.hibernate.tutorial.domain6.Address@71dc3d org.hibernate.tutorial.domain6.TUser@1326484 org.hibernate.tutorial.domain6.Address@16546ef
它的每個(gè)結(jié)果都是相應(yīng)查出來(lái)的對(duì)象。
2)left outer join,這個(gè)相當(dāng)于SQL的左連接,我們直接看一下例子:
from TUser usr left outer join fetch usr.addresses
當(dāng)我們運(yùn)行上面的語(yǔ)句時(shí),hibernate打印出:
Hibernate: select tuser0_.id as id1_0_, addresses1_.id as id0_1_, tuser0_.name as name1_0_, tuser0_.age as age1_0_, addresses1_.address as address0_1_, addresses1_.user_id as user3_0_1_, addresses1_.user_id as user3_0__, addresses1_.id as id0__ from t_user tuser0_ left outer join t_address addresses1_ on tuser0_.id=addresses1_.user_id
我們?cè)趍ysql中進(jìn)行查出,看到:

我們看到,盡管shun4沒(méi)有對(duì)應(yīng)的adress,但還是把它查出來(lái),left outer join是指把左邊表的記錄全部查出。
沒(méi)有fetch的情況這里就不講了。
3)接下來(lái)我們看一下right outer join,看名字肯定就和left outer join有點(diǎn)關(guān)系的,我們直接看例子就可以明顯看出了。
from TUser usr right outer join fetch usr.addresses
我們執(zhí)行它,得到Hibernate輸出的結(jié)果語(yǔ)句為:
Hibernate: select tuser0_.id as id1_0_, addresses1_.id as id0_1_, tuser0_.name as name1_0_, tuser0_.age as age1_0_, addresses1_.address as address0_1_, addresses1_.user_id as user3_0_1_, addresses1_.user_id as user3_0__, addresses1_.id as id0__ from t_user tuser0_ right outer join t_address addresses1_ on tuser0_.id=addresses1_.user_id

這里我們可以看到address為T(mén)est4的并沒(méi)有相應(yīng)的user與它對(duì)應(yīng),但它還是并查出來(lái)了,right outer join是指把右邊表的記錄全部查出。
fetch的情況如上,如果不明白可以看一下inner join fetch。
相關(guān)文章
如何解決LocalDateTime傳值JSON格式化問(wèn)題
這篇文章主要介紹了如何解決LocalDateTime傳值JSON格式化問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
java簡(jiǎn)單實(shí)現(xiàn)斗地主發(fā)牌功能
這篇文章主要為大家詳細(xì)介紹了java簡(jiǎn)單實(shí)現(xiàn)斗地主發(fā)牌功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
springboot過(guò)濾器執(zhí)行兩次的解決及跨域過(guò)濾器問(wèn)題
這篇文章主要介紹了springboot過(guò)濾器執(zhí)行兩次的解決及跨域過(guò)濾器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
全面掌握J(rèn)ava中的循環(huán)控制語(yǔ)句與條件判斷語(yǔ)句的使用
這篇文章主要介紹了Java中的循環(huán)控制語(yǔ)句與條件判斷語(yǔ)句的使用,循環(huán)和判斷是Java編程中流程控制的基礎(chǔ),需要的朋友可以參考下2016-02-02
Java實(shí)現(xiàn)的讀取資源文件工具類ResourcesUtil實(shí)例【可動(dòng)態(tài)更改值的內(nèi)容】
這篇文章主要介紹了Java實(shí)現(xiàn)的讀取資源文件工具類ResourcesUtil,結(jié)合實(shí)例形式分析了java針對(duì)資源文件的讀取與修改相關(guān)操作技巧,需要的朋友可以參考下2017-10-10

