Hibernate之HQL基本用法
关于HQL
HQL与SQL非常类似,只不过SQL的操作对象是数据表,列等对象,而HQL操作的是持久化类,实例,属性等。
HQL是完全面向对象的查询语言,因此也具有面向对象的继承,多态等特性。
使用HQL的一般步骤为:
获取session对象
编写HQL语句
使用session的createQuery方法创建查询对象(Query对象)
使用SetXxx(index/para_name, value)为参数复制
使用Query对象的list()方法返回查询结果列表(持久化实体集)
下面演示一下HQL的基本用法,演示之前先附上之前的一个例子,双向N-N关联映射,
假设有下面两个持久化类Person和Event之间成N-N双向关联,代码如下,
Person类
package hql; import java.util.*; import javax.persistence.*; @Entity
@Table(name = "person_inf")
public class Person
{
@Id @Column(name = "person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
@ManyToMany(cascade=CascadeType.ALL, targetEntity=MyEvent.class)
@JoinTable(name = "person_event" ,
joinColumns = @JoinColumn(name = "person_id"
, referencedColumnName="person_id"),
inverseJoinColumns = @JoinColumn(name = "event_id"
, referencedColumnName="event_id")
)
private Set<MyEvent> myEvents
= new HashSet<>();
@ElementCollection(targetClass=String.class)
@CollectionTable(name="person_email_inf",
joinColumns=@JoinColumn(name="person_id" , nullable=false))
@Column(name="email_detail" , nullable=false)
private Set<String> emails
= new HashSet<>(); public void setId(Integer id)
{
this.id = id;
}
public Integer getId()
{
return this.id;
} public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
} public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return this.age;
} public void setMyEvents(Set<MyEvent> myEvents)
{
this.myEvents = myEvents;
}
public Set<MyEvent> getMyEvents()
{
return this.myEvents;
} public void setEmails(Set<String> emails)
{
this.emails = emails;
}
public Set<String> getEmails()
{
return this.emails;
}
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
} }
Event类
package hql; import java.util.*; import javax.persistence.*; @Entity
@Table(name="event_inf")
public class MyEvent
{
@Id @Column(name="event_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String title;
private Date happenDate;
@ManyToMany(targetEntity=Person.class , mappedBy="myEvents")
private Set<Person> actors
= new HashSet<>(); public void setId(Integer id)
{
this.id = id;
}
public Integer getId()
{
return this.id;
} public void setTitle(String title)
{
this.title = title;
}
public String getTitle()
{
return this.title;
} public void setHappenDate(Date happenDate)
{
this.happenDate = happenDate;
}
public Date getHappenDate()
{
return this.happenDate;
} public void setActors(Set<Person> actors)
{
this.actors = actors;
}
public Set<Person> getActors()
{
return this.actors;
}
public MyEvent() {}
public MyEvent(String title, Date happenDate) {
this.title = title;
this.happenDate = happenDate;
}
}
PersonManager类
package hql; import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration; import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.HashSet; public class PersonManager
{ public static void testPerson() throws ParseException
{
Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
Person p1 = new Person("张三",20);
p1.getEmails().add("zhangsan@baidu.com");
p1.getEmails().add("zhangsan@google.com"); Person p2 = new Person("李四",30);
p2.getEmails().add("lisi@jd.com"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
MyEvent e1 = new MyEvent("大学毕业", sdf.parse("2012-06-01"));
MyEvent e2 = new MyEvent("参加工作", sdf.parse("2012-10-01"));
MyEvent e3 = new MyEvent("出国旅游", sdf.parse("2013-05-01"));
MyEvent e4 = new MyEvent("回家过年", sdf.parse("2013-12-20"));
MyEvent e5 = new MyEvent("升职加薪", sdf.parse("2014-01-01")); p1.getMyEvents().add(e1);
p1.getMyEvents().add(e3);
p1.getMyEvents().add(e4);
p1.getMyEvents().add(e5); p2.getMyEvents().add(e2);
p2.getMyEvents().add(e3);
p2.getMyEvents().add(e4); sess.save(p1);
sess.save(p2); tx.commit();
sess.close();
} public static void main(String[] args) throws ParseException {
testPerson();
}
}
首先执行上面的PersonManager,我们需要生成数据表如下,
MariaDB [test]> select * from event_inf; |
MariaDB [test]> select * from person_event; |
MariaDB [test]> select * from person_inf; |
MariaDB [test]> select * from person_email_inf; |
HQL的基本用法
现在可以写一个查询类用来查询上面的数据,一个最简单的查询是使用session的createQuery方法返回一个Query对象,再用Query对象的list()方法返回结果集,
通常结果集是持久化实体的结果集,可以用类型强制转换还原成原来的持久化类的对象,例如下面这样,
public static void findPersons() {
Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); List pl = sess.createQuery("select distinct p from Person p "
+ "join p.myEvents where title = :eventTitle")
.setString("eventTitle", "出国旅游") //执行setString()为参数赋值
.list(); //Query()调用list()方法获取查询的全部实例
for (Object ele : pl) {
Person p = (Person)ele;
System.out.println(p.getName());
}
tx.commit();
sess.close();
sf.close();
}
上面是最基本的查询方法,HQL语法与SQL非常类似,只不过HQL查询针对的是持久化类,实例及属性,上面查询的是持久化类的实例集合,
在设置参数的时候,可以在HQL中使用冒号(:)后紧接参数名来作为一个参数的占位符,然后在setXXX()为参数赋值,上面代码执行结果如下,
Hibernate: select distinct person0_.person_id as person_i1_3_, person0_.age as age2_3_, person0_.name as name3_3_ from person_inf person0_ inner join person_event myevents1_ on person0_.person_id=myevents1_.person_id inner join event_inf myevent2_ on myevents1_.event_id=myevent2_.event_id where title=?
张三
李四
也可以在HQL中使用问号(?)紧接索引的方式设置参数占位符,然后在setXXX()中用按索引为参数赋值,例如下面,
public static void findPersonsByHappendDate() throws ParseException {
Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
List pl = sess.createQuery("select distinct p from Person p "
+ "inner join p.myEvents event where event.happenDate "
+ "between ?1 and ?2")
.setDate("1", sdf.parse("2012-06-01"))
.setDate("2", new Date())
.list();
for (Object ele : pl) {
Person p = (Person)ele;
System.out.println(p.getName());
}
tx.commit();
sess.close();
sf.close();
}
上面执行结果,
Hibernate: select distinct person0_.person_id as person_i1_3_, person0_.age as age2_3_, person0_.name as name3_3_ from person_inf person0_ inner join person_event myevents1_ on person0_.person_id=myevents1_.person_id inner join event_inf myevent2_ on myevents1_.event_id=myevent2_.event_id where myevent2_.happenDate between ? and ?
张三
李四
除了在select中查询持久化类实例之外,也能直接查询属性,例如,
public static void findPersonProperty() {
Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); List pl = sess.createQuery("select distinct p.id, p.name, p.age "
+ "from Person p join p.myEvents")
.list();
for (Object ele : pl) {
Object[] objs = (Object[])ele;
System.out.println(java.util.Arrays.toString(objs));
}
tx.commit();
sess.close();
sf.close();
}
程序运行结果,
Hibernate: select distinct person0_.person_id as col_0_0_, person0_.name as col_1_0_, person0_.age as col_2_0_ from person_inf person0_ inner join person_event myevents1_ on person0_.person_id=myevents1_.person_id inner join event_inf myevent2_ on myevents1_.event_id=myevent2_.event_id
[1, 张三, 20]
[2, 李四, 30]
关联和连接
在HQL中最简单的查询语句是from子句(前面不需要select关键字),from后接持久化了类名称(大小写敏感),持久化类可以用有别名,用as关键字(可以省略)。
from后面可以接多个实体类进行表关联,但是实际上这种用法不多,更多的是使用隐式或者显示连接实现跨表连接。
隐式连接
不使用join关键字,而使用点号(.)来隐式连接实体,例如 "from Person p where p.address = xxxx"
但是需要注意的是,在Hibernate 3.2.3之后,隐式连接的使用需要特别注意,当关联的是普通组件时候,可以使用隐式连接,如果关联的是集合属性,就会抛出illegal attempt to dereference collection...异常。
显式连接
HQL中支持以下显示连接,分别与SQL99中的各种连接对应
inner join, 可简写成join
left outer join, 可简写成left join
right outer join, 可简写成right join
full join
下面是一个显式连接的例子,通过打印出来的SQL语句会发现,HQL会自动根据持久化类之间的关联关系,生成对应的连接表的 with (等同于SQL语句里join的on关键字)条件,即使没有在HQL中显式地写出with条件。
HQL:
"select p from Person p inner join p.emails e where e = :email"
SQL:
Hibernate:
select
person0_.person_id as person_i1_3_,
person0_.age as age2_3_,
person0_.name as name3_3_
from
person_inf person0_
inner join
person_email_inf emails1_
on person0_.person_id=emails1_.person_id
where
emails1_.email_detail=?
查询结果集
延迟加载
Hibernate默认开启了延迟加载,如果session关闭,则无法继续通过实体对象获取数据。
例如Person关联的属性emails,默认加载Person时候并不会去获取emails属性值,一旦session关闭就无法获取emails了,
为了解决这个问题,可以在HQL中使用join fetch关键字,例如下面的例子,
public static void joinFetch() {
Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); List pl = sess.createQuery("from Person p join fetch p.myEvents")
.list();
tx.commit();
sess.close();
sf.close(); for(Object ele : pl) {
Person p = (Person)ele;
System.out.println(p.getMyEvents().iterator().next().getTitle());
}
}
我们将读取数据放在session关闭之后,发现依然可以后去myEvents属性,
Hibernate:
select
person0_.person_id as person_i1_3_0_,
myevent2_.event_id as event_id1_0_1_,
person0_.age as age2_3_0_,
person0_.name as name3_3_0_,
myevent2_.happenDate as happenDa2_0_1_,
myevent2_.title as title3_0_1_,
myevents1_.person_id as person_i1_3_0__,
myevents1_.event_id as event_id2_2_0__
from
person_inf person0_
inner join
person_event myevents1_
on person0_.person_id=myevents1_.person_id
inner join
event_inf myevent2_
on myevents1_.event_id=myevent2_.event_id
出国旅游
出国旅游
出国旅游
出国旅游
参加工作
参加工作
参加工作
Select 子句
select子句接单个持久化类
如果select后面只查询单个持久化类,那么返回的查询结果是一个集合,每一个集合元素可以直接通过强制类型转换还原成原来的数据类型,例如
List list0 = sess
.createQuery("select p from Person p").list();
首先可以用for(Object ele : list0)遍历结果集,对每一个元素可以直接强制转换成原来的数据类型,
for (Object ele : list0) {
Person p = (Person)ele;
System.out.println(p.getName());
}
select子句接持久化类和属性混合
select子句可以接持久化类,或者其属性,通常select子句查询的结果就是一个集合,集合的每个元素都是数组,相当于返回的是一个二维数组结果集,
List pl = sess
.createQuery(
"select p.name, e from Person p join p.myEvents e "
+ "where e.title = :title")
.setString("title", "回家过年").list();
因此需要先将每个集合元素还原成数组,再次将数组每一项强制换换成对应数据类型,
for (Object ele : pl) {
Object[] objs = (Object[]) ele;
String name = (String) objs[0];
MyEvent e = (MyEvent) objs[1];
System.out.println(name + "," + e.getTitle());
}
首先上面sess.createQuery(xxx).list()返回的是一个集合,因此用for(Object ele : pl)遍历每一个元素,每一个元素又是一个数组,因此用Object[] objs = (Object[])ele; 强制转换去还原集合每一个元素,而对于数组每一项的数据类型,则是根据HQL中select后面的顺序,逐一匹配,因此在for循环里用来String和MyEvent来还原数组每一项。
这是select最通用的用法。
select 子句直接生成list对象或者map对象
Query对象返回的集合中,每一个元素就是一个list对象,每个list对象,例如像下面这样,
List list1 = sess
.createQuery(
"select new list(p.name, p.age, e) from Person p left join p.emails e")
.list();
返回的集合中,每一个元素就是list对象,用强制类型转换还原即可,遍历每一个list对象,就是select后面list中的每个元素,
for (Object ele : list1) {
List name = (List) ele;
Iterator it = name.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
当然也可以在select子句之后直接生成map对象,使用别名p.name as pname作为map的key,其实际值作为value,
相当于Query对象通过.list()方法返回的结果集中,包含了n个map对象,每个map对象里都只有一个key-value对,每个key名字都交pname,
List list2 = sess
.createQuery(
"select new map(p.name as pname) from Person p")
.list();
可以像下面这样遍历map,
for (Object ele : list2) {
Map mName = (HashMap) ele;
Set keySet = (Set)mName.keySet();
Iterator it = keySet.iterator();
while( it.hasNext()) {
Object key = it.next();
System.out.println(key+"->"+mName.get(key));
}
}
输出结果,
pname->张三
pname->李四
select子句甚至可以直接跟持久化类的构造函数
List list3 = sess
.createQuery(
"select new MyEvent(e.title, e.happenDate) from Person p left join p.myEvents e")
.list();
这样查询出的效果跟直接查询一个持久化类一样,只不过这里是用select的结果去初始化一个持久化类了,依然可以遍历每一个集合元素,直接强制转换成对应持久化类实例,
for (Object ele : list3) {
MyEvent e = (MyEvent)ele;
System.out.println(e.getTitle());
}
多态查询
HQL支持多态,from后跟持久化类名,不仅会查出持久化类的全部实例,还会查出该类的子类的全部实例。
即,当我们用HQL查询父类或者接口时,父类的子类,或者接口的实现类的实例都会一起被查询出来。
注意,一张数据表代表一个持久化类,表中一行就带表一个持久化类的实例。
所以按照多态查询的规则,如果在我们前面的测试工程中,查询 java.lang.Object类会有什么结果呢,例如
List list4 = sess.createQuery("from java.lang.Object o").list();
分析上面的HQL,结合HQL的多态性质, 我们知道Object是所有java类的父类,在本工程中,Person和MyEvent都是Object的子类,所以这两个类的所有实例都将被查询出来,
Person_inf表中有两条记录,event_inf表中有5条记录,所以最终会总共会查询出7个实例,我们直接将每个实例的内存映射打印出来,
for(Object ele : list4) {
System.out.println(ele);
}
首先我们会看到Hibernate生成了两条SQL语句,分别用来查询person_inf和event_inf表,这个可以理解的,因为本工程中Object有两个子持久化类。
Hibernate:
select
person0_.person_id as person_i1_3_,
person0_.age as age2_3_,
person0_.name as name3_3_
from
person_inf person0_
Hibernate:
select
myevent0_.event_id as event_id1_0_,
myevent0_.happenDate as happenDa2_0_,
myevent0_.title as title3_0_
from
event_inf myevent0_
打印的实例的内存映射如下,
hql.Person@350c420a
hql.Person@e6c75827
hql.MyEvent@75ae7c20
hql.MyEvent@62ee0677
hql.MyEvent@214e7ddc
hql.MyEvent@cfad90e0
hql.MyEvent@226c3fff
可以看到内存映射中,也是按持久化类的顺序排列的,我们甚至可以将每一个实例的内存映射进行强制类型转换,还原成真正的持久化类的对象,比如下面这样,
Person p1 = (Person)list4.get(0);
Person p2 = (Person)list4.get(1);
System.out.println(p1.getName()+","+p2.getName()); MyEvent e1 = (MyEvent)list4.get(2);
MyEvent e2 = (MyEvent)list4.get(3);
MyEvent e3 = (MyEvent)list4.get(4);
MyEvent e4 = (MyEvent)list4.get(5);
MyEvent e5 = (MyEvent)list4.get(6);
System.out.println(e1.getTitle()+","+e2.getTitle()+","+e3.getTitle()+","
+ ""+e4.getTitle()+","+e5.getTitle());
现在就可以直接用对象去访问属性了,上面程序片段输出结果为,
张三,李四
出国旅游,升职加薪,大学毕业,回家过年,参加工作
可见刚好将每张表的所有记录(即实例)打印出来了!
where子句
引用关联属性的隐式连接和显示连接
where子句后面可以可以使用属性来限定范围,属性可以是普通属性或者组件属性,但是需要特别注意集合属性。
在3.2.3以后的版本中,如果属性为集合属性,那么不能直接在where子句后面使用点号(.)来访问,因为这在底层会转换成多表连接查询,即隐式连接。 3.2.3之后的版本是不支持集合属性的隐式连接的,需要显示join连接。
例如下面这个查询,Person的myEvents是一个集合属性,p.myEvents对应的是一个关联的MyEvent实体,在底层会隐式连接person_inf和event_inf表。
List list0 = sess.createQuery("select p from Person p where p.myEvents.title is not null").list();
抛出异常,
Exception in thread "main" org.hibernate.QueryException: illegal attempt to dereference collection [person0_.person_id.myEvents] with element property reference [title] [select p from hql.Person p where p.myEvents.title is not null]
at org.hibernate.QueryException.generateQueryException(QueryException.java:137)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:120)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:234)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:158)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:131)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:93)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:167)
at org.hibernate.internal.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:301)
at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:236)
at org.hibernate.internal.SessionImpl.createQuery(SessionImpl.java:1836)
at hql.HqlQuery.testWhere(HqlQuery.java:245)
at hql.HqlQuery.main(HqlQuery.java:265)
这将引发集合属性隐式连接异常,抛出 illegal attempt to dereference collection 的错误,
必须将上面的查询改为显示连接,
List list0 = sess.createQuery("select p from Person p inner join p.myEvents e "
+"where e.title is not null").list();
用特殊关键字【id】引用任何主键
不论持久化类中的标识属性(表关键字)定义成什么名字,在HQL中都可以用关键字id来代替,例如
在MyEvent持久化类中,定义了eventId为标识属性,
@Entity
@Table(name="event_inf")
public class MyEvent
{
@Id @Column(name="event_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer eventId;
...
在HQL中,使用id引用持久化类的标识属性,
List list1 = sess.createQuery("from MyEvent e where e.id = 1").list();
子查询
HQL中支持在select和where后面进行子查询,例如这样,
List list1 = sess.createQuery("select (select id from MyEvent e where id=1) from MyEvent "
+ "where id = (select id from Person p where id = 2)").list();
上面代码将生成以下SQL语句,可以看到HQL子查询与SQL子查询基本一致。
Hibernate:
select
(select
myevent1_.event_id
from
event_inf myevent1_
where
myevent1_.event_id=1) as col_0_0_
from
event_inf myevent0_
where
myevent0_.event_id=(
select
person2_.person_id
from
person_inf person2_
where
person2_.person_id=2
)
命名查询(注解查询)
Hibernate提供了一个@NamedQuery注解可以将原本写在createQuery()中的HQL放在注解中,之后通过sess.getNamedQuery()取出注解上的配置进行查询,一样会返回Query对象,后续跟普通查询流程一样,如下面的例子,
在Person实体类上我们增加一个命名查询注解
@Entity
@Table(name = "person_inf")
@NamedQuery(name="myNamedQuery", query="select p from Person as p where p.age > ?")
public class Person
{
...
调用方法如下,
List list1 = sess.getNamedQuery("myNamedQuery")
.setInteger(0, 25)
.list();
for(Object ele : list1) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
命名查询的本质只是将java代码中的HQL放在注解中去配置了。
条件查询
条件查询需要使用sess.createCriteria(Class)来返回Criteria对象,一个Criteria对象就代表一次查询,通过Criteria对象的add方法可以添加查询条件, 查询条件通过工具类Restrictions中的方法来指定,例如下面这样,
List list1 = sess.createCriteria(Person.class)
.add( Restrictions.gt("age", 25))
.list();
其中工具类Restrictions支持很多静态方法,用来做查询条件,例如 gt代表“大于”,lt代表“小于”等等。
之后就能得到查询结果集,和之前的处理方法一样。
for(Object ele : list1) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
关联属性实体的条件查询
如果要在关联属性的实体上增加查询条件,就需要对关联属性再次使用 createCritieria()方法,例如要在Person的关联属性myEvents上增加条件查询,
List list2 = sess.createCriteria(Person.class)
.add( Restrictions.gt("age", 25))
.createCriteria("myEvents", JoinType.LEFT_OUTER_JOIN)
.add( Restrictions.isNotNull("title"))
.list();
for(Object ele : list2) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
createAlias()
createAlias()也可以实现在关联属性上增加条件查询,与createCritieria()不同的是,createAlias()仅仅是给关联实体起一个别名,让后面的过滤条件可以根据该关联实体的别名进行筛选,而不是创建一个新的Criteria实例。
List list3 = sess.createCriteria(Person.class)
.add( Restrictions.gt("age", 25))
.createAlias("myEvents", "eve")
.add( Restrictions.isNotNull("eve.title"))
.list();
for(Object ele : list3) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
条件查询上的延迟加载
与HQL中的fetch关键字一样,Critieria实例也可以增加延迟加载配置,使用setFetchMode()即可,有三个可选值,
DEFAULT:使用配置文件指定延迟加载策略
JOIN:使用外连接、预初始化关联实体.(即不使用延迟加载)
SELECT:启用延迟加载,系统将使用单独的select语句来初始化关联实体,之后真正要访问关联实体的时候,才会执行第二天条select语句。
下面是一个启用延迟加载的例子,
List list4 = sess.createCriteria(Person.class)
.add( Restrictions.gt("age", 25))
.setFetchMode("myEvents", FetchMode.SELECT)
.list();
tx.commit();
sess.close();
sf.close(); for(Object ele : list4) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
Set<MyEvent> e = p.getMyEvents();
Iterator it = e.iterator();
while (it.hasNext()) {
MyEvent ee = (MyEvent)it.next();
System.out.println(ee.getTitle());
}
}
我们在第3行启用延迟加载,那么一旦session关闭之后,关联实体就不能访问了,在第12行会抛出 failed to lazily initialize a collection of role 的异常,
查看Hibernate生成的SQL,会发现只查询了person_inf表,
Hibernate:
select
this_.person_id as person_i1_3_0_,
this_.age as age2_3_0_,
this_.name as name3_3_0_
from
person_inf this_
where
this_.age>?
李四,30
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: hql.Person.myEvents, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:576)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:215)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:555)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:143)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
at hql.HqlQuery.testCriteria(HqlQuery.java:353)
at hql.HqlQuery.main(HqlQuery.java:372)
如果我们将上面代码第3行改成.setFetchMode("myEvents", FetchMode.JOIN),即关闭延迟加载,那么关联属性就会立即查询出来,可以看到生成的SQL使用left out join进行了连接查询。
Hibernate:
select
this_.person_id as person_i1_3_1_,
this_.age as age2_3_1_,
this_.name as name3_3_1_,
myevents2_.person_id as person_i1_3_3_,
myevent3_.event_id as event_id2_2_3_,
myevent3_.event_id as event_id1_0_0_,
myevent3_.happenDate as happenDa2_0_0_,
myevent3_.title as title3_0_0_
from
person_inf this_
left outer join
person_event myevents2_
on this_.person_id=myevents2_.person_id
left outer join
event_inf myevent3_
on myevents2_.event_id=myevent3_.event_id
where
this_.age>?
李四,30
回家过年
出国旅游
参加工作
李四,30
回家过年
出国旅游
参加工作
李四,30
回家过年
出国旅游
参加工作
投影,聚合,分组
Hibernate的条件查询中的所谓的投影运算就是按列查询,具体分成两种,一种是根据列来进行统计,使用Projection接口实现,类似于SQL中的聚集函数(count,AVG,groupby 等)
另一种就是直接按列查询,Hibernate的条件查询中使用Property()方法,其作用类似SQL中的select
Projection投影运算
所谓HQL中的投影,聚合,分组,其实就是SQL中的一些聚合函数,例如统计记录条数count(), 计算平均值avg(),统计最大值max(),以及分组统计group by等等。
在条件查询中可以通过Projection接口实现这些功能。工具类Projections提供了很多静态方法来实现上面的功能,下面是基本用法,
List list1 = sess.createCriteria(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Projections.projectionList()
.add(Projections.rowCount())
.add(Projections.max("eve.title"))
.add(Projections.groupProperty("eve.title")))
.list();
for(Object ele : list1) {
Object[] objs = (Object[])ele;
for (Object obj : objs) {
System.out.print(obj+",");
}
System.out.println("\n==========");
}
看看hibernate生成的SQL就能知道Projection的功能了,
Hibernate:
select
count(*) as y0_,
max(eve1_.title) as y1_,
eve1_.title as y2_
from
person_inf this_
inner join
person_event myevents3_
on this_.person_id=myevents3_.person_id
inner join
event_inf eve1_
on myevents3_.event_id=eve1_.event_id
group by
eve1_.title
其实就是在SQL中使用了一些聚集函数统计而已,统计结果如下,
2,出国旅游,出国旅游,
==========
1,升职加薪,升职加薪,
==========
1,参加工作,参加工作,
==========
2,回家过年,回家过年,
==========
1,大学毕业,大学毕业,
==========
指定别名及排序
可以为Projection方式的聚集统计结果起一个别名,用来根据统计结果排序等。通常会有三种方式起别名。
第一种是使用Projections工具的Alias()方法
List list2 = sess.createCriteria(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Projections.projectionList()
.add(Projections.alias(Projections.rowCount(), "c"))
.add(Projections.max("eve.title"))
.add(Projections.groupProperty("eve.title")))
.addOrder(Order.asc("c"))
.list();
for(Object ele : list2) {
Object[] objs = (Object[])ele;
for (Object obj : objs) {
System.out.print(obj+",");
}
System.out.println("\n==========");
}
上面是按照记录条数排序,结果如下,
1,升职加薪,升职加薪,
==========
1,大学毕业,大学毕业,
==========
1,参加工作,参加工作,
==========
2,出国旅游,出国旅游,
==========
2,回家过年,回家过年,
==========
第二种方法是使用SimpleProject的as()方法指定别名
这种方法要求Projections后的聚集函数必须是SimpleProject类或者子类,
List list3 = sess.createCriteria(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Projections.projectionList()
.add(Projections.rowCount())
.add(Projections.max("eve.title"))
.add(Projections.groupProperty("eve.title").as("c")))
.addOrder(Order.asc("c"))
.list();
for(Object ele : list3) {
Object[] objs = (Object[])ele;
for (Object obj : objs) {
System.out.print(obj+",");
}
System.out.println("\n==========");
}
上面是按照title排序,结果如下,
2,出国旅游,出国旅游,
==========
1,升职加薪,升职加薪,
==========
1,参加工作,参加工作,
==========
2,回家过年,回家过年,
==========
1,大学毕业,大学毕业,
==========
第三种是使用ProjectList的重载方法add()时指定别名
List list4 = sess.createCriteria(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Projections.projectionList()
.add(Projections.rowCount(),"c")
.add(Projections.max("eve.title"))
.add(Projections.groupProperty("eve.title")))
.addOrder(Order.asc("c"))
.list();
for(Object ele : list4) {
Object[] objs = (Object[])ele;
for (Object obj : objs) {
System.out.print(obj+",");
}
System.out.println("\n==========");
}
上面也是按照记录条数排序
Property投影运算
除了Projections工具类之外,Property方法也可以进行投影运算,但Projections主要是用来进行统计计算,而Property则主要是用来选择指定的类,作用类似SQL中的select.
基本用法如下,
List list6 = sess.createCriteria(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Projections.projectionList()
.add(Property.forName("name"))
.add(Property.forName("eve.title")))
.add(Property.forName("eve.title").eq("回家过年"))
.list();
for(Object ele : list6) {
Object[] objs = (Object[])ele;
for (Object obj : objs) {
System.out.print(obj+",");
}
System.out.println("\n==========");
}
上面代码的意思是,先选出Person类的name属性和MyEvent类的title属性(当然事先需要对这两个持久化类进行关联查询),然后按照title过滤,只选出title为“回家过年”的记录,
Hibernate生成的SQL如下,
select
this_.name as y0_,
eve1_.title as y1_
from
person_inf this_
inner join
person_event myevents3_
on this_.person_id=myevents3_.person_id
inner join
event_inf eve1_
on myevents3_.event_id=eve1_.event_id
where
eve1_.title=?
查询结果如下,
张三,回家过年,
==========
李四,回家过年,
==========
DetachedCriteria离线查询和子查询
这里所谓的离线查询,就是在session打开之前定义好查询语句,获取一个DetachedCriteria的实例,这样可以在任意的session中使用这个查询对象,
例如这样,
DetachedCriteria query = DetachedCriteria
.forClass(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Property.forName("name"));
之后在任意的session中,使用DetachedCriteria类的getExecutableCriteria()可以调用这个查询实例进行离线查询,
例如这样,
List list1 = query.getExecutableCriteria(sess).list();
另外,如果在session使用了条件查询,在条件查询中又使用了Projection的eq(), eqAll(), gt(), in()...等一系列类似运算符的方法,这时可以将session外面定义的DetachedCriteria放在eq(), eqAll(), gt(), in()中作为一个子查询,
例如这样,
List list2 = sess.createCriteria(Person.class)
.add( Property.forName("name").in(query))
.list();
也就是说,当直接使用DetachedCriteria对象的getExecutableCriteria()进行查询的时候,DetachedCriteria对象就是一个离线查询,
当在session中使用DetachedCriteria查询的时候,就是一个子查询,
下面是一个完整的离线查询和子查询的例子,
public static void testDetachedCriteria() {
DetachedCriteria query = DetachedCriteria
.forClass(Person.class)
.createAlias("myEvents", "eve")
.setProjection(Property.forName("name")); Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(Person.class);
conf.addAnnotatedClass(MyEvent.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); List list1 = query.getExecutableCriteria(sess).list();
15 System.out.println(list1); List list2 = sess.createCriteria(Person.class)
.add( Property.forName("name").in(query))
.list();
System.out.println(list2);
tx.commit();
sess.close();
sf.close();
}
对于上面第14行的离线查询,将会看到下面这样的SQL
Hibernate:
select
this_.name as y0_
from
person_inf this_
inner join
person_event myevents3_
on this_.person_id=myevents3_.person_id
inner join
event_inf eve1_
on myevents3_.event_id=eve1_.event_id
对于上面第17行的子查询,将会看到下面这样的SQL
Hibernate:
select
this_.person_id as person_i1_3_0_,
this_.age as age2_3_0_,
this_.name as name3_3_0_
from
person_inf this_
where
this_.name in (
select
this_.name as y0_
from
person_inf this_
inner join
person_event myevents3_
on this_.person_id=myevents3_.person_id
inner join
event_inf eve1_
on myevents3_.event_id=eve1_.event_id
)
原生SQL查询
Hibernate还支持原生的SQL查询,但是通常不建议在新项目中这么做,而是用在老系统上。使用session的createSQLQuery()方法可以用原生SQL进行查询并得到Query对象,后续用法与之前一样。
标量查询
即直接查出值(而不是实体类对象),因为是直接查出的值,需要使用Query对象的addScalar()筛选指定的列,并为其制定数据类型
比如这样,
String sqlString = "select p.* from person_inf p";
List list1 = sess.createSQLQuery(sqlString)
.addScalar("name", StandardBasicTypes.STRING)
.addScalar("age",StandardBasicTypes.INTEGER)
.list();
for(Object ele : list1) {
Object[] row = (Object[])ele;
System.out.println(row[0]+","+row[1]);
}
实体查询
也可以将SQL查出的结果转换成实体类,前提条件是必须查出所有列才行。使用Query对象的addEntity()方法可以将查询结果集转换成实体类,
比如这样,
List list2 = sess.createSQLQuery(sqlString)
.addEntity(Person.class)
.list();
for(Object ele : list2) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
涉及到多表查询的时候,可以同时转换多个实体类,比如这样,
String sqlString3 = "select p.*,pe.*,e.* from person_inf p, person_event pe, event_inf e "
+ "where p.person_id = pe.person_id "
+ "and e.event_id = pe.event_id";
List list3 = sess.createSQLQuery(sqlString3)
.addEntity("p",Person.class)
.addEntity("e", MyEvent.class)
.list();
转为普通javabean
可以使用Query对象的setResultTransformer()方法,
String sqlString4 = "select p.name, p.age from person_inf p";
List list4 = sess.createSQLQuery(sqlString4)
.setResultTransformer(Transformers.aliasToBean(Student.class))
.list();
关联实体类处理
使用Query对象的addJoin()方法可以将原生SQL中的部分查询结果转换为关联的实体类,例如下面,
String sqlString5 = "select * from person_inf p, person_event pe, event_inf e "
+ "where p.person_id = pe.person_id "
+ "and e.event_id = pe.event_id";
List list5 = sess.createSQLQuery(sqlString5)
.addEntity("p", Person.class)
.addJoin("e", "p.myEvents")
.list();
对比上面的多表查询转换为实体类的例子,发现与这里的关联实体类处理结果是一样的。
命名SQL
可以使用配置文件或者注解,将SQL从源码中拿出来单独管理,以实现程序解耦。
单一实体类SQL查询
Hibernate中使用@NamedNativeQuery注解来定义命名SQL查询,通常其结构如下,
@NamedNativeQuery(name="simpleQuery"
, query="select e.event_id as person_id, e.title as name, 1 as age from event_inf e"
, resultClass=Person.class)
可以看到命名SQL查询的注解中,通常有三个参数,其中resultClass这个参数可以指定查出的结果将要转换成哪个实体类的实例。
在程序中,将会这样调用SQL命名查询,
List list6 = sess.getNamedQuery("simpleQuery").list();
然后在session关闭之后,依然可以遍历list中的数据,我们在前面指定了此命名SQL查询将返回Person类的实例,所以下面直接将结果转换,
for (Object ele : list6) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
多个实体类、结果集查询
命名SQL不仅可以返回实体类,同时还可以返回结果集,最终的返回结果将是包含实体类和普通结果集的混合体,
这种情况下, resultClass属性已经无法满足了,我们需要使用resultSetMapping来指定将会返回什么,而resultMapping属性又依赖于@SqlResultSetMapping注解的定义。
下面是一个会返回多个实体类,结果集混合查询注解的例子,首先需要定义命名SQL查询注解,
@NamedNativeQuery(name = "queryTest"
, query = "select p.*,e.*,p.name from person_inf p, person_event pe, event_inf e "
+ "where p.person_id = pe.person_id " + "and e.event_id = pe.event_id"
, resultSetMapping = "firstMapping")
我们指定返回方式为resultSetMapping, 并指定代称为firstMapping,下面需要配置@SqlResultSetMapping来指定返回的结果集,
@SqlResultSetMapping(name = "firstMapping"
, entities = {
@EntityResult(entityClass = Person.class),
@EntityResult(entityClass = MyEvent.class, fields = {
@FieldResult(name="eventId", column="e.event_id"),
@FieldResult(name = "title", column = "e.title"),
@FieldResult(name = "happenDate", column = "e.happenDate"), }) }
, columns = { @ColumnResult(name = "p.name", type = String.class) })
即,在Query的结果集的每一行中,我们需要先返回Person实体类实例,再返回MyEvent实体类实例,还要返回一个字符串,那么对应读取结果集的代码如下,
for(Object ele : list7) {
Object[] ents = (Object[])ele;
Person p = (Person)ents[0];
MyEvent e = (MyEvent)ents[1];
String name = (String)ents[2];
System.out.println(p.getName()+","+e.getTitle()+","+name);
}
调用存储过程
Hibernate是通过命名SQL查询来调用存储过程的,只需将存储过程函数放入命名SQL注解的query参数中。
下面我们在mysql中定义一个存储过程如下,
create PROCEDURE select_all_event()
select e.event_id as person_id, e.title as name, 1 as age from event_inf e
然后将存储过程函数名放入命名SQL注解的query参数中,
@Entity
@Table(name="event_inf")
@NamedNativeQuery(name="simpleQuery"
, query="{call select_all_event()}"
, resultClass=Person.class)
public class MyEvent
{
之后在代码中的调用方法与普通的执行命名SQL的方法没有区别,
List list1 = sess.getNamedQuery("myNamedQuery")
.setInteger(0, 25)
.list();
for(Object ele : list1) {
Person p = (Person)ele;
System.out.println(p.getName()+","+p.getAge());
}
使用定制SQL
当我们持久化一个实体的时候(调用session.save()或session.persist()),Hibernate会自动为我们生产成SQL语句,
但是如果我们不想使用Hibernate自动生成的SQL,而是希望自定义SQL呢。
那么我们可以使用Hibernate的定制SQL的注解:@SQLInsert, @SQLUpdate,@SQLDelete,@SQLDeleteAll等等,例如下面这样,
@SQLInsert(sql="insert into student_inf(student_id,age,name) values(100,?,?)")
@SQLUpdate(sql="update student_inf set name=?,age=? were person_id=?")
@SQLDelete(sql="delete from student_inf where person_id=?")
@SQLDeleteAll(sql="delete from student_inf")
@Entity
@Table(name="student_inf")
public class Student {
@Id @Column(name = "student_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
我们定制了4个SQL语句,其中在insert语句中,我们故意让student_id=100,
在持久化的代码中没有任何特殊,
Student s = new Student("abc",22);
sess.save(s);
在不定制SQL的情况下,Hibernate将为我们生成 Hibernate: insert into student_inf (age, name) values (?, ?) 这样的SQL语句插入数据,
而在定制SQL的情况下,我们发现Hibernate使用的是我们定制的SQL语句来插入数据,
Hibernate: insert into student_inf(student_id,age,name) values(100,?,?)
查看mysql数据库,发现确实插入了一条id为100的记录,
MariaDB [test]> select * from student_inf;
+------------+-----+------+
| student_id | age | name |
+------------+-----+------+
| 1 | 22 | abc |
| 100 | 22 | abc |
+------------+-----+------+
2 rows in set (0.00 sec)
数据过滤
在Hibernate中可以使用@Filter进行数据过滤,其实就是将HQL中的where条件提取到注解中,有点类似于命名SQL一样。
使用@Filter 之前需要先使用@FilterDef定义过滤器,将过滤器应用到具体实体类或者关联属性之后,还需要在session中开启过滤器。下面是一个例子,
首先在Person实体类上定义一个过滤器,
//定义一个过滤器
@FilterDef(name="eDate"
,parameters={@ParamDef(name="eff_start_date", type="date"),@ParamDef(name="eff_end_date", type="date")})
@Entity
@Table(name = "person_inf")
public class Person
{
接着我们在一个关联属性上使用这个过滤器,
@ManyToMany(cascade=CascadeType.ALL, targetEntity=MyEvent.class)
@JoinTable(name = "person_event" ,
joinColumns = @JoinColumn(name = "person_id"
, referencedColumnName="person_id"),
inverseJoinColumns = @JoinColumn(name = "event_id"
, referencedColumnName="event_id")
)
@Filter(name="eDate"
, condition="happenDate BETWEEN :eff_start_date and :eff_end_date")
private Set<MyEvent> myEvents
= new HashSet<>();
之后,我们还需要在session中开启过滤器,后续装载实体类时,就会自动应用这个过滤器,
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sess.enableFilter("eDate")
.setParameter("eff_start_date", sdf.parse("2013-06-01"))
.setParameter("eff_end_date", sdf.parse("2015-06-01"));
List list1 = sess.createQuery("select distinct e from Person p join p.myEvents e").list();
for( Object ele : list1 ) {
MyEvent e = (MyEvent)ele;
System.out.println(e.getTitle()+","+e.getHappenDate());
}
我们看到,Hibernate生成的SQL,自动添加了过滤条件,
Hibernate:
select
distinct myevent2_.event_id as event_id1_0_,
myevent2_.happenDate as happenDa2_0_,
myevent2_.title as title3_0_
from
person_inf person0_
inner join
person_event myevents1_
on person0_.person_id=myevents1_.person_id
inner join
event_inf myevent2_
on myevents1_.event_id=myevent2_.event_id
and myevent2_.happenDate BETWEEN ? and ?
回家过年,2013-12-20 00:00:00.0
升职加薪,2014-01-01 00:00:00.0
Hibernate之HQL基本用法的更多相关文章
- JavaWeb_(Hibernate框架)Hibernate中数据查询语句HQL基本用法
HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. ...
- Hibernate学习-Hibernate查询语言HQL
HQL(Hibernate Query Language)Hibernate查询语言,语法类似于SQL,可以直接使用实体类及属性. 使用HQL 可以避免使用JDBC 查询的一些弊端 不需要再编写繁复的 ...
- Hibernate五 HQL查询
HQL查询一 介绍1.HQL:Hibernate Query Language,是一种完全面向对象的查询语言.使用Hibernate有多重查询方式可供选择:hibernate的HQL查询,也可以使用条 ...
- Hibernate学习---QBC_hibernate完整用法
QBC(Query By Criteria) API提供了检索对象的另一种方式,它主要有Criteria接口.Criterion接口和Expresson类组成,它支持在运行时动态生成查询语句. Hib ...
- hibernate的hql查询
1.概念介绍 1.Query是Hibernate的查询接口,用于从数据存储源查询对象及控制执行查询的过程,Query包装了一个HQL查询语句. 2.HQL是Hibernate Query Langua ...
- Hibernate之HQL查询
一.Hibernate 提供了以下几种检索对象的方式: 导航对象图检索方式: 根据已经加载的对象导航到其他对象 OID 检索方式: 按照对象的 OID 来检索对象 HQL 检索方式:使用面向对象的 H ...
- Hibernate之HQL介绍
Hibernate中提供了多种检索对象的方式,主要包括以下种类: 导航对象图检索方式:根据已经加载的对象导航到其他对象 OID检索方式:根据对象的OID来检索对象 HQL检索方式:使用面向对象的HQL ...
- Hibernate 的hql查询简介【申明:来源于网络】
Hibernate 的hql查询简介[申明:来源于网络] Hibernate 的hql查询简介:http://blog.csdn.net/leaf_130/article/details/539329 ...
- Hibernate的hql语句save,update方法不执行
Hibernate的hql语句save,update方法不执行 可能出现的原因问题: 未进行事务管理 需要进行xml事务配置或者注解方式的事务配置
随机推荐
- LeetCode: Word Ladder [126]
[题目] Given two words (start and end), and a dictionary, find the length of shortest transformation s ...
- Web前端开发实战6:CSS实现导航菜单结合二级下拉式菜单的简单变换
前面几篇博文都在讲导航菜单和二级下拉式菜单,事实上有非常多方法都能够实现的.详细的情况还要视情况而定. 在后面学习到jQuery框架之后,会有更丰富的动画效果.因为在学习Ajax和jQuery的初步阶 ...
- C++数组类模板
* 作为数组类模板,肯定没有vector做得好,可是普通的数组有1个优点就是能直接操作内存.vector在这方面就不是非常方便了. 网上尽管也有数组类模板.多维的设计基本上都不是非常好.我这个类模板多 ...
- oc08--局部变量,全局变量,函数方法的区别
// // main.m // 局部变量和全局变量以及成员变量的区别 #import <Foundation/Foundation.h> @interface Person : NSObj ...
- 【BZOJ3218】【UOJ#77】a + b Problem
题目 题目在这里 思路&做法 明显的最小割(其实是之前做过一道类似的题) S向每一个格子连容量为\(b_i\)的边 每一个格子向T连容量为\(w_i\)的边 对于格子\(i\)向满足条件的格子 ...
- 移动端的click事件延迟触发的原理是什么?如何解决这个问题?
移动端的click事件延迟触发的原理是什么?如何解决这个问题? 原理 :移动端屏幕双击会缩放页面 300ms延迟 会出现点透现象 在列表页面上创建一个弹出层,弹出层有个关闭的按钮,你点了这个按钮关闭弹 ...
- css要点
1.对inline-block设置overflow: hidden会造成baseline移动,因此需要设置vertical-align才不会出现样式问题. 2.使用flex时,需要对设置flex: 1 ...
- vue-cli3+typescript+router
vue基于类的写法,和基于对象的写法并不一致.使用vue-cli3创建的项目,src目录下的文件结构并没有多大区别,store.router.app.view.components.aeests该有的 ...
- 推荐10个超棒的jQuery工具 提示插件
脚本之家 http://www.jb51.net/article/28525.htm
- ML二:NNSearch数据结构--二叉树
wiki百科:http://zh.wikipedia.org/wiki/%E5%86%B3%E7%AD%96%E6%A0%91%E5%AD%A6%E4%B9%A0 opencv学习笔记--二杈决策树: ...