数据查询,装载
1. Session---单数据加载---load/ get
Load方法根据指定的实体类和id从数据库装载认为存在的一条记录. 应该确保对象确实存在, 否则会抛出ObjectNotFoundException.
Load方法可返回实体的代理类实例, 可充分利用内部缓存和二级缓存中的现有数据.
get方法根据指定的实体类和id从数据库查询并装载一条记录.数据不存在将得到null.
get方法返回的永远是实体类. 只在内部缓存中进行数据查找, 如果没有数据就调用SQL完成数据读取.
- 出于性能考虑,避免无谓的数据库访问,Session在调用数据库查询功能之前,会先在缓存中进行查询。首先在第一级缓存中,通过实体类型和id进行查找,如果第一级缓存查找命中,且数据状态合法,则直接返回。
- 之后,Session会在当前 “NonExists”记录中进行查找,如果 “NonExists”记录中存在同样的查询条件,则返回null。“NonExists”记录了当前 Session实例在之前所有查询操作中,未能查询到有效数据的查询条件。如果Session中一个无效的查询条件重复出现,即可迅速做出判断。
- 对于load方法而言,如果内部缓存中没有发现有效数据,则查询第二级缓存,如果第二级缓存命中,则返回。
- 如果在缓存中未发现有效数据,则发起数据库查询操作(Select SQL).
- 如果经过查询未发现对应记录,则将此次查询的信息在 “NonExists”中加以记录,并返回null。
- 否则根据映射配置和Select SQL得到的 ResultSet,创建对应的数据对象. 并将其数据对象纳入当前Session实体管理容器(一级缓存)。
- 执行Interceptor.onLoad方法(如果有对应的Intercepeor)。
- 将数据对象纳入二级缓存。
- 如果数据对象实现了LifeCycle接口,则调用数据对象的onLoad方法。
- 返回数据对象。
2. Session---批量数据查询---find/iterate|createQuery().list/iterate
Hibernate2的find/iterate分别返回list和Iterator, Hibernate3中,上述方法已经从Session接口中废除,统一由Query接口提供。Hibernate3将Session的createQuery()方法得到的Query对象调用list/iterate方法实现相同的功能. 从实现体制而言,这两个版本之间并没有什么差异。
find/ list方法通过一条select sql实现查询; 而iterate则执行1+ N次查询, 它首先执行select sql获取满足条件的id, 再根据每个id获取对应的记录.
find方法将执行Select HQL从数据库中获得所有符合条件的记录并构造相应的实体对象,实体对象构建完毕之后,就将其纳入缓存。为之后的iterate方法提供了现成的可用数据。
这样,之后iterate方法执行时,它首先执行一条Select SQL以获得所有符合查询条件的数据id,随即,iterate方法首先再本地缓存中根据id查找对应的实体对象是否存在(类似Session.load方法),如果缓存中已经存在对应的数据,则直接以此数据对象作为查询结果,如果没找到,再执行相应的Select语句获得对应的库表记录(iterate方法如果执行了数据库读取操作并构建了完整的数据对象,也会将其查询结果纳入缓存)。
根据java面向对象的继承层次,Object是所有类的父类,所以使用面对对象的持久化框架Hibernate可以执行下述操作:
public static void testHibernateOO() {
Session s = HibernateSession3.getSession();
Iterator<Object> itor =(Iterator<Object>)s.createQuery("FROM java.lang.Object").list();
while(itor.hasNext()){
Object rowData = itor.next();
System.out.println(rowData.getClass()+":");
}
}
3. Session批量数据查询与缓存利用的示例
如果执行下面的代码:
Tuser data =null;
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
如果执行下面的代码:
Tuser data =null;
List<Tuser> datas = hibernate_session.createQuery(" From Tuser").list();
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
System.out.println("\r\nUse iterate");
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
iterate:Washing:[aisee@163.com]
如果执行下面的代码:
Tuser data =null;
List<Tuser> datas = hibernate_session.createQuery(" From Tuser").list();
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
System.out.println("\r\nUse iterate");
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser"). iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
System.out.println("\r\nUse list AGAIN");
datas = hibernate_session.createQuery(" From Tuser").list();
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
iterate:Washing:[aisee@163.com]
Use list AGAIN
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
Use list
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
如果执行:
Tuser data =null;
System.out.println("\r\nUse list");
List<Tuser> datas = hibernate_session.createQuery(" From Tuser").list();
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
hibernate_session.close();
hibernate_session = MySessionFactory.getSession();
System.out.println("\r\nUse iterate");
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
System.out.println("\r\nUse list AGAIN");
datas = hibernate_session.createQuery(" From Tuser").list();
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
Use list
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
Use list AGAIN
Hibernate: select tuser0_.id as id2_, tuser0_.name as name2_, tuser0_.email as email2_ from T_user t
user0_
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
如果执行下面的代码:
System.out.println("\r\nUse iterate");
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
System.out.println("\r\nUse iterate AGAIN");
dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
Use iterate AGAIN
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
iterate:Washing:[aisee@163.com]
如果执行下面的代码:
System.out.println("\r\nUse iterate");
Iterator<Tuser> dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
hibernate_session.close();
hibernate_session = MySessionFactory.getSession();
System.out.println("\r\nUse iterate AGAIN");
dataItor = hibernate_session.createQuery(" From Tuser").iterate();
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
Use iterate AGAIN
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id2_0_, tuser0_.name as name2_0_, tuser0_.email as email2_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
find方法(hibernate2)/Query的list(hibernate3)实际上不利用缓存,它对缓存只写不读。而iterate方法(hibernate2)/Query的iterate(hibernate3)则可以充分发挥缓存带来的优势,如果目标数据只读或者读取相对较为频繁,通过这种机制可以大大减少性能上的损耗。对于批量数据, hibernate可以自动延迟加载:
把数据库数据扩大后:
String hsql = " From Tuser as t where t.id<501";
Tuser data =null;
long end =0;
System.out.println("\r\nUse iterate");
long begin =System.currentTimeMillis();
Iterator<Tuser> dataItor = hibernate_session.createQuery(hsql).iterate();
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
while(dataItor.hasNext()){
data = dataItor.next();
System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_ where tuser0_.id<501
Time be userd:15
Hibernate: select tuser0_.id as id0_0_, tuser0_.name as name0_0_, tuser0_.email as email0_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:AiSee11652:[1w@11, 1w@11, as@tsts.com]
Hibernate: select tuser0_.id as id0_0_, tuser0_.name as name0_0_, tuser0_.email as email0_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
Hibernate: select tuser0_.id as id0_0_, tuser0_.name as name0_0_, tuser0_.email as email0_0_ from T_
user tuser0_ where tuser0_.id=?
iterate:Washing:[aisee@163.com]
....
执行:
String hsql = " From Tuser as t where t.id<100001";
Tuser data =null;
System.out.println("\r\nUse list");
long end =0;
long begin =System.currentTimeMillis();
System.out.println("\r\nUse iterate");
begin =System.currentTimeMillis();
Iterator<Tuser> dataItor = hibernate_session.createQuery(hsql).iterate();
while(dataItor.hasNext()){
data = dataItor.next();
//System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
System.out.println("\r\nUse iterate AGAIN");
begin =System.currentTimeMillis();
dataItor = hibernate_session.createQuery(hsql).iterate();
while(dataItor.hasNext()){
data = dataItor.next();
//System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
}
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
Use list
Use iterate
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_ where tuser0_.id<100001
Time be userd:4078
Use iterate AGAIN
Hibernate: select tuser0_.id as col_0_0_ from T_user tuser0_ where tuser0_.id<100001
Time be userd:3797
如果是用list, 不会利用任何缓存,更不会延迟加载:
System.out.println("\r\nUse list");
long end =0;
long begin =System.currentTimeMillis();
List<Tuser> datas = hibernate_session.createQuery(hsql).list();
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
for(int i=0;i<datas.size();i++){
data = datas.get(i);
System.out.println("list:" + data.getName() + ":" + data.getEmail());
}
Use list
Hibernate: select tuser0_.id as id0_, tuser0_.name as name0_, tuser0_.email as email0_ from T_user t
user0_ where tuser0_.id<501
Time be userd: 78
list:AiSee11652:[1w@11, 1w@11, as@tsts.com]
list:Washing:[aisee@163.com]
list:Washing:[aisee@163.com]
list:Washing:[aisee@163.com]
list:Washing:[aisee@163.com]
list:Washing:[aisee@163.com]
如果数据量较大, 结合iterate方法和Session/SessionFactory的evict方法逐条对记录进行处理,并将数据对象强制从缓存中移除, 将内存消耗保持再可以接受的范围之内。查询50万条:
System.out.println("\r\nUse iterate");
begin =System.currentTimeMillis();
Iterator<Tuser> dataItor = hibernate_session.createQuery(hsql).iterate();
while(dataItor.hasNext()){
data = dataItor.next();
data.getName();
//System.out.println("iterate:" + data.getName() + ":" + data.getEmail());
hibernate_session.evict(data);
factory.evict(Tuser.class,data.getId());
}
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
Use iterate
Time be userd:2113969
直接查询并保存在缓存:
System.out.println("\r\nUse iterate");
begin =System.currentTimeMillis();
Iterator<Tuser> dataItor = hibernate_session.createQuery(hsql).iterate();
while(dataItor.hasNext()){
data = dataItor.next();
data.getName();
//out.println("iterate:" + data.getName() + ":" + data.getEmail());
// hibernate_session.evict(data);
// factory.evict(Tuser.class,data.getId());
}
end =System.currentTimeMillis();
System.out.println("Time be userd:"+(end-begin));
out.println("Time be userd:"+(end-begin)+"<br />");
Use iterate
ERROR: 2007-01-13 20:55:28,687: StandardWrapperValve[line:253}: Servlet.service() for servlet jsp th
rew exception
java.lang.OutOfMemoryError: Java heap space
所以数据量过大, 应该避免使用find(list), 而使用iterate(iterate),并且手动清除hibernate的一,二级缓存。
4. Query Cache(阑尾)
Query Cache中保存了之前查询操作执行过的Select SQL,以及由此查询产生的查询结果集 (包括查询对象的类型和id)。
之后发生查询请求的时候,Hibernate会首先根据查询的 SQL从Query Cache中检索,如果此SQL曾经执行过,则取出对应这个SQL的检索结果集,再根据这个结果集中的对象类型及其id,从缓存中取出对应的实体对象返回。
Query Cache中缓存的SQL及其结果集并非永远存在,当Hibernate发现此SQL对应的库表发生了变动(Update/Delete/Insert),会自动将Query Cache中对应表的 SQL缓存废除。因此,Query Cache只在特定的情况下产生作用:
- 完全相同的Select SQL重复执行。
- 再两次查询之间,此Select SQL对应的库表没有发生过改变。
由于以上两个条件的严格限制,Query Cache再实际应用中的意义并没有我们想象中的那么重大,因此,Hibernate在默认情况下也关闭了这个特性。
为了启用Query Cache,我们必须在Hibernate配置文件(hibernate.cfg.xml)中打开配置hibernate.cache.use_query_cache为ture,之后我们必须在Query 的查询执行之前, 调用Query的setQureyCache(true); 而且后面的查询想利用该缓存也需要调用Query的setQureyCache(true). 这样find(list)就可以利用Query Cache缓存了.
鉴于配置和使用比较麻烦, 所以用得不多.
5. Hibernate2的find/iterate的批量查询条件
find(String) throws HibernateException;
find(String, Object, Type) throws HibernateException;
find(String, Object[], Type[]) throws HibernateException;
iterate(String)
iterate(String, Object, Type)
iterate(String, Object[], Type[])
其中的字符串参数表示一个可带查询参数的HQL查询字符串, 其中的每个 ? 代表一个参数, 而对应的Object[]参数表示每个?的值, 而Type[]数组代表每个参数的数值类型. 取自net.sf.hibernate.type.Type类的实现. net.sf.hibernate.type包下面有很多XXXType的实现类, 也可以使用net.sf.hibernate.Hibernate类的NullableType类型静态常量属性(表示Type类型的一个实现. 比如Hibernate.STRING表示一个字符串类型).
AddUserForm inData = (AddUserForm) form;
hsql =" From webapp.hibernate.pojo.UserPoJo As user2 Where user2.tel=? and user2.user=?";
s.find(hsql,new Object[]{inData.getTel(), inData.getUser()},
new Type[]{new net.sf.hibernate.type.StringType(), new net.sf.hibernate.type.StringType()});
/* //或者:
s.find( hsql,
new Object[]{ inData.getTel(), inData.getUser() } ,
new Type[]{ Hibernate. STRING, Hibernate.STRING } );
*/
6. Hibernate2/3的Criteria批量查询条件
org.hibernate.criterion. Restrictions 及其final子类org.hibernate.criterion.Expression提供了从Criteria对象中增加查询条件的实现.
Hibernate3的HQL的查询条件可以用Expression的相关方法.
Criteria criteria = session.createCriteria(Tuser.class);
criteria.add(Expression.eq("name", "Erica"));
数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate提供了灵活多样的查询机制。其中包括:1. Criteria Query, 2. Hibernate Query Language (HQL), 3. SQL
Criteria Query通过面向对象化的设计,将数据查询条件封装为一个对象。简单来讲,Criteria Query可以看作是传统SQL的对象化表示,如:
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
criteria.add(Expression.eq("sex",new Integer(1)));
这里的criteria 实例实际上是SQL “Select * from t_user where name=’Erica’ and sex=1”的封装(我们可以打开Hibernate 的show_sql 选项,以观察Hibernate在运行期生成的SQL语句)。
Hibernate 在运行期会根据Criteria 中指定的查询条件(也就是上面代码中通过criteria.add方法添加的查询表达式)生成相应的SQL语句。
这种方式的特点是比较符合Java 程序员的编码习惯,并且具备清晰的可读性。正因为此,不少ORM实现中都提供了类似的实现机制(如Apache OJB)。
对于Hibernate的初学者,特别是对SQL了解有限的程序员而言,Criteria Query无疑是上手的极佳途径,相对HQL,Criteria Query提供了更易于理解的查询手段,借助IDE的Coding Assist机制,Criteria的使用几乎不用太多的学习。
7. Criteria 查询表达式, Criterion
org.hibernate.criterion. Expression extends Restrictions, Restrictions的众多方法对应了SQL的限制条件, 自然也被Expression类继承. (特别地拥有and, or, sql等方法.) 那些方法返回一个Criterion接口的实现, 而这正式Criteria的add方法的参数类型. Criteria 本身只是一个查询容器,具体的查询条件需要通过Criteria.add方法添加到Criteria实例中。
Expression 对象具体描述了查询条件。Expression提供了对应的查询限定机制,包括:
Expression.eq 对应SQL“field = value”表达式。 如Expression.eq("name","Erica")
Expression.allEq 参数为一个Map对象,其中包含了多个属性-值对应关系。相当于多个Expression.eq关系的叠加。
Expression.gt 对应SQL中的 “field > value ” 表达式
Expression.ge 对应SQL中的 “field >= value” 表达式
Expression.lt 对应SQL中的 “field < value” 表达式
Expression.le 对应SQL中的 “field <= value” 表达式
Expression.between 对应SQL中的 “between” 表达式
如下面的表达式表示年龄(age)位于13到50区间内。
Expression.between("age",newInteger(13),new Integer(50));
Expression.like 对应SQL中的 “field like value” 表达式
Expression.in 对应SQL中的 ”field in …” 表达式
Expression.eqProperty 用于比较两个属性之间的值,对应SQL中的“field= field”。如:Expression.eqProperty("TUser.groupID","TGroup.id");
Expression.gtProperty 用于比较两个属性之间的值,对应SQL中的“field> field”。
Expression.geProperty 用于比较两个属性之间的值,对应SQL中的“field>= field”。
Expression.ltProperty 用于比较两个属性之间的值,对应SQL中的“field< field”。
Expression.leProperty 用于比较两个属性之间的值,对应SQL中的“field<= field”。
Expression.and and关系组合。
如:
Expression.and(Expression.eq("name","Erica"),Expression.eq("sex",new Integer(1)));
Expression.or or关系组合。如:
Expression.or(Expression.eq("name","Erica"),Expression.eq("name","Emma"));
Expression.sql 作为补充,本方法提供了原生SQL语法的支持。我们可以通过这个方法直接通过SQL语句限定查询条件。
下面的代码返回所有名称以“Erica”起始的记录:
Expression.sql(“lower({alias}.name) like lower(?)”,"Erica%",Hibernate.STRING);
其中的“{alias}”将由Hibernate在运行期使用当前关联的POJO别名替换。
注意Expression 各方法中的属性名参数(如Express.eq中的第一个参数),这里所谓属性名是POJO中对应实际库表字段的属性名(大小写敏感),而非库表中的实际字段名称。
Criteria,Query ,排序, 分页
8. 局部属性的级连数据排序
两种排序方式: 1. Sort 2. order-by, 前者通过JVM完成, 后者由数据库完成.
- sort :可排序Set在Hibernate中对应为net.sf.hibernate.collection. SortedSet类, 实现了java.util.SortedSet . 在set元素中可配置sort属性(sort='natural', 指定采用Java默认排序机制, 通过调用数据类型的compareTo方法. 可以自定义java.util.Comparator接口的实现, 来作为sort的属性值, 而实现自定义的排序算法. Map类型与Set基本一致, 但Bag和List不支持sort排序.
- order-by: 在元素中增加order-by属性(比如order-by="address desc" )可以实现数据库排序. 该特性利用了JDK1.4+ 中的LinkedHashSet以及LinkedHashMap, 由此必须在环境JDK1.4以上才可成功. Set, Map, Bag支持, List不支持该特性.
9. Criteria/Query分页, Criteria排序
限定返回的记录范围
通过Criteria(或者Query)的setFirstResult, setMaxResults 方法可以限制一次查询返回的记录范围:
Criteria criteria = session.createCriteria(TUser.class);
//限定查询返回检索结果中,从第一百条结果开始的20条记录
criteria.setFirstResult(99); //Base 0
criteria.setMaxResults(20);
对查询结果进行排序
//查询所有groupId=2的记录 //并分别按照姓名(顺序)和groupId(逆序)排序
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("groupId",new Integer(2)));
criteria.addOrder(Order.asc("name"));
criteria.addOrder(Order.desc("groupId"));
Criteria作为一种对象化的查询封装模式,不过由于Hibernate在实现过程中将精力更加集中在HQL查询语言上,因此Criteria的功能实现还没做到尽善尽美(这点上,OJB的Criteria 实现倒是值得借鉴),因此,在实际开发中,建议还是采用Hibernate 官方推荐的查询封装模式:HQL。
Hibernate Query Language (HQL)
Criteria提供了更加符合面向对象编程模式的查询封装模式。不过,HQL(Hibernate Query Language)提供了更加强大的功能,在官方开发手册中,也将HQL作为推荐的查询模式。相对Criteria,HQL提供了更接近传统SQL语句的查询语法,也提供了更全面的特性。最简单的一个例子:
String hql = "from org.hibernate.sample.TUser";
Query query = session.createQuery(hql);
List userList = query.list();
上面的代码将取出TUser的所有对应记录。如果我们需要取出名为“Erica”的用户的记录,类似SQL,我们可以通过SQL 语句加以限定:
String hql = "from org.hibernate.sample.TUser as user where user.name='Erica'";
Query query = session.createQuery(hql);
List userList = query.list();
其中我们新引入了两个子句“as”和“where”,as子句为类名创建了一个别名,而where子句指定了限定条件。
HQL子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。
关于HQL,Hibernate 官方开发手册中已经提供了极其详尽的说明和示例,详见本文HQL部分或者Hibernate官方开发手册。
延迟加载(Lazy Loading)
所谓延迟加载,就是在需要数据的时候,才真正执行数据加载操作, 避免性能浪费。
Hibernate 2中的延迟加载实现主要针对:实体对象和集合(Collectio)。Hibernate 3同时提供了属性的延迟加载功能。hibernate2默认lazy=false; 而hibernate3默认为true.
10. 实体对象的延迟加载
在关于Session.get/load方法的描述中,我们曾经提到,通过load方法我们可以指定可以返回目标实体对象的代理。
正常情况下,一个非延迟加载运行时,Hibernate已经从库表中取出了对应的记录,并构造了一个完整的TUser对象。
通过class的lazy="true"属性, 使用了延迟加载机制之后, user对象属性均为null,此时并没有任何Hibernate并没有执行数据库查询操作。原因就在于Hibernate的代理机制。Hibernate中引入了CGLib作为代理机制实现的基础。CGLib可以在运行期动态生成Java Class。这里的代理机制,其基本实现原理就是通过由CGLib构造一个包含目标对象所有属性和方法的动态对象(相当于动态构造目标对象的一个子类)返回,并以之作为中介,为目标对象提供更多的特性。
从上面的内存快照可以看到,真正的TUser对象位于代理类的属性中。当我们调用user.getName方法时,调用的实际上是代理类的getName( )方法, 它会首先检查CGLIB$CALLBACK_0.target中是否存在目标对象。如果存在,则调用目标对象的getName方法返回,如果目标对象为空,则发起数据库查询指令,读取记录、构建目标对象并将其投入"代理类的.target"。
这样,用过一个中间代理,实现了数据延迟加载功能,只有当客户程序真正调用实体类的取值方法时,Hibernate才会执行数据库查询操作。
11. 集合类型的延迟加载
Hibernate延迟加载机制中,关于集合的延迟加载特性意义最为重大,也时实际应用中相当重要的一个环节。
回到开篇提到的一个例子:
如,之前示例中TUser对象在加载的时候,在非“延迟加载”的情况下,会同时读取其所关联的多个地址(adress)对象,对于确实需要对address进行操作的应用逻辑而言,关联数据的自动加载机制的确非常有效。
但是,如果我们只是想要获得user的年龄(age)属性,而不关心user的地址(address)信息,那么自动加载address的特性就显得多余,并且造成了极大得性能浪费。为了获得user的性别属性,我们可能还要同时从数据库中读取数条无用的地址数据,这导致了大量无谓的系统开销。
对于我们这里TUser对象的加载过程,如果要做到集合的延迟加载,也就意味着,加载TUser对象时只针对其本身的属性,而当我们需要获取TUser对象所关联的address信息时(如执行user.getAddresses时),才真正从数据库中加载address数据并返回。
我们将前面一对多关系中的lazy属性修改为true,即指定了关联对象采用延迟加载:
尝试执行以下代码:
运行时抛出异常:
如果我们稍做调整,将session.close放在代码末尾,则不会发生这样的问题。
这意味着,只有我们实际加载user关联的address时,Hibernate才试图通过session从数据库中加载实际的数据集,而由于我们读取address之前已经关闭了session,所以出现了以上的错误。
这里有个问题,如果我们采用了延迟加载机制,但希望在一些情况下,实现非延迟加载时的功能,也就是说,我们希望在Session关闭后,依然允许操作user的addresses属性。如,为了向View层提供数据,我们必须提供一个完整的User对象,包含其所关联的sddress信息,而这个User对象必须在Session关闭之后仍然可以使用。
Hibernate.initialize方法可以强制Hibernate立即加载关联对象集:
为了实现透明化的延迟加载机制,Hibernate进行了大量努力。其中包括JDK Collection接口的独立实现。
如果我们尝试用HashSet强制转化Hibernate返回的Set型对象:
就会在运行期得到一个java.lang.ClassCastException,实际上,此时返回的是一个Hibernate的特定Set实现“net.sf.hibernate.collection.Set”,而非传统意义上的JDK Set实现。
这也正是我们为什么在编写POJO时,必须用JDK Collection Interface (如Set,Map),而非特定的JDK Collection实现类(如HashSet、HashMap)的原因(如private Set addresses;而非private HashSet addresses)。
回到前面TUser类的定义:
我们通过Set接口,声明了一个addresses属性,并创建了一个HashSet作为addresses的初始实例,以便我们创建TUser实例后,就可以为其添加关联的addrsee对象:
此时,这里的addresses属性是一个HashSet对象,其中包含了一个address对象的引用。
前面的 “脏数据检查”部分中,我们讨论过针对无关联实体的保存。那么,在现在的情况下,当前调用session.save(user)时,Hibernate如何处理其关联的Address对象集?
通过Eclipse的Debug视图,我们可以看到session.save方法执行前后user对象发生的变化。
可以看到,user对象在通过Hibernate处理之后已经发生了变化。
首先,由于insert操作,Hibernate获得数据库产生的id值(在我们的例子中,采用native方式的主键生成机制),并填充到user对象的id属性。这个变化比较容易理解。
另一方面,Hibernate使用了自己的Collection实现“net.sf. hibernate.collection.Set”对user中的HashSet型addresses属性进行了替换,并用数据对其进行填充,保证新的addresses与原有的adresses包含同样的实体元素。
再来看下面的代码:
根据之前的讨论我们知道,当代码执行到(1)处时,addresses数据集尚未读入,我们得到的addSet对象实际上只是一个未包含任何数据的net.sf.hibernate.collection.Set实例。
代码运行至(2),真正的数据读取操作才开始执行。观察一下net..sf..hibernate.collection.Set.iterator方法我们可以看到:
直到此时,真正的数据加载(read( )方法)才开始执行。
Read方法将首先在缓存中查找是否有符合条件的数据索引。
注意这里数据索引的概念,Hibernate在对集合类型进行缓存时,分两部分保存,首先是这个集合中所有实体的id列表(也就是所谓的数据索引,对于这里的例子,数据索引中包含了所有userid=1的adress对象的id清单),其次是各个实体对象。
如果没有发现对应的数据索引,则执行一条Select SQL(对于本例就是select…fromt_address where user_id=?)获得所有符合条件的记录,接着构造实体对象和数据索引后返回。实体对象和数据索引也同时被分别纳入缓存。
另一方面,如果发现了对应的数据索引,则从这个数据索引中取出所有id列表,并根据id列表依次从缓存中查询对应的数据,则执行相应的Select SQL获得对应的address记录(对于本例就是select…from t_adress wahere id+?)。
这里引出了另外一个性能关注点,即关联对象的缓存策略。
如果我们为某个集合类设定了缓存,如:
注意这里的<cache usage= “read-only”/>只会使得Hibernate对数据索引进行缓存,也就是说,这里的配置实际上只是缓存了集合中的数据索引,而并不包括这个集合中的各个实体元素。
执行下面的代码:
观察屏幕日志输出:
看到,第二次获取关联的addresses集合的时候,执行了3次Select SQL。
正是由于<set…><cache usage= “read-only”/>…</set>的设定,第一次addresses集合被加载之后,数据索引已经被放入缓存。
第二次再加载addresses集合的时候,Hibernate再缓存中发现了这个数据索引,于是从索引里面取出当前所有的id(此时数据库中有3条符合的记录,所以共获得3个id),r然后依次根据这3个id在缓存中查找对应的实体对象,但是没有找到,于是发起了数据库查询,由Select SQL根据id从t_address表中读取记录。
我们看到,由于缓存中数据索引的存在,似乎SQL执行的次数更多了,这导致第二次借助缓存的数据查询比第一次性能开销更大。
导致这个问题出现的原因何在?
这是由于我们至为集合类型配置了缓存,这样Hibernate只会缓存数据索引,而不会将集合中的实体元素同时也纳入缓存。
我们必须为集合类型中的实体对象也指定缓存策略,如:
此时,Hibernate才会对集合中的实体也进行缓存。
再次运行之前的代码,得到以下日志输出:
可以看到,第二次查询没有执行任何SQL即宣告完成,所有的数据都来自缓存,这无意对性能的提升有着及其重要的意义。
上面我们探讨了 net.sf. hibernate.collection.Set.iterate方法,同样,观察 net.sf.hibernate.collection.Set.size/isEmpty方法或者其他hibernate collection中的同类型方法实现,我们可以看到同样的处理方式。
通过自定义Collection类型实现数据延迟加载的原理也就再于此。
这样,通过自定义Collection实现,Hibernate就可以在Collection层从容的实现延迟加载特性。只有程序真正读取这个Collection的内容时,才激发底层数据库操作,这为系统的性能提供了更加灵活的调整手段。
12. 属性的延迟加载
在前面的内容中,我们讨论了关于实体,及其关联集合对象的延迟加载机制。这些机制为改进持久层性能提供了一个重要渠道。
根据我们已有的经验来看,上面这两种延迟加载模式,实质上都是面向数据实体。我们可以决定是否即刻加载某个实体,或者某个实体集合。
如果需要对实体的某个部分(如某个属性)应用延迟加载策略,我们应如何入手?在基础篇中,我们曾经探讨了有关实体粒度设计的主题。通过对同一库表建立不同粒度的实体映射关系,我们可以变通的实现库表的部分加载,不过,这并非我们这里所说的延迟加载,另一方面,这样需要付出大量的额外工作。
另外,我们也可以在HQL中通过Select子句限定加载的属性列表。不过,随之而来HQL语句的琐碎语法实在令人厌倦。
在Hibernate 2中,为了避免实体加载可能带来的性能浪费,我们只能采取以上两种策略。Hibernate团队显然也意识到了这个问题, Hibernate3中通过property节点的lazy属性可以为特定属性指定延迟加载.但还需要借助类增强器对二进制class文件进行强化处理.(buildtime bytecode instrumentation).
数据保存
13. Session.save方法
接受一个实体对象,执行时:
- 在Session内部缓存中寻找对象, 如果数据已经保存(Persistent状态), 则直接返回.即使状态已经变化, 也将在脏数据检查中判定, 并执行相应update操作.
- 如果实体类实现了lifecyle接口, 则调用待保存对象的onSave方法.
- 如果实体类实现了Validatable接口, 则调用其validate()方法.
- 如果有, 调用拦截器(Interceptor)的onSave方法.
- 构造Insert SQL并执行. 可能会设置对象的id值.
- 将对象放入内部缓存. (不纳入二级缓存, 因为Hibernate认为在事务的剩余部分被修改的几率很高).
- 如果存在级连关系, 则递归处理.
14. Session.update方法
可以把对象从Detached状态转换为Persisitent状态.
- 根据实体对象的id在Session缓存中查找, 如果发现, 则认为对象已经处于Persistent状态, 直接返回.(说明Persistent状态的数据调用update不起作用.)
- 初始化对象的状态信息, 并纳入缓存. 本身并不发送Update SQL完成更新操作, 但是Session.flush方法将执行Update SQL.(Transaction.commit在提交数据库事务之前调用了Session.flush)
15. Session.saveOrUpdate方法
执行步骤:
- 在Session缓存中查找, 找到就直接返回.
- 如果有, 执行实体类的Interceptor.isUnsave(), 判断对象是否为未保存.
- 未保存(Transient), 则调用save方法保存.
- 如果已经保存(Detached), 则调用update时对象与Session关联.
数据批量操作
16. 数据批量操作--保存
保存的数据将纳入缓存.由于二级缓存可以配置最大容量, 但内部缓存却没有容量限制, 所以批量操作中, 应该考虑到控制内部缓存的过度增长而出现OutOfMemeoryError错误.
可以在数据保存过程中周期性的对Session调用flush和clear方法, 确保Session的容量不至于太大.
也可以设置hibernate.jdbc.batch_size参数, 指到批量操作中每次提交SQL的数量. (MySql Driver不支持BatchUpdate).
17. 数据批量操作--删除--hibernate2
轻量级的ORM都面临一个问题, 数据库的改变需要在缓存中同步. 所以表面上的一个HQL删除, 往往执行的是1+N次操作. 第一次是查询, 第二次是逐条删除.
由此的问题:
- 内存消耗.如果数据量过大, find方法很容易导致OutOfMemeoryError错误. 可以考虑使用iterate方法逐条获取数据在执行删除. Hibernate2.16以后的版本, 提供了基于游标的数据遍历操作(前提是JDBC驱动必须支持游标), 通过游标, 我们可以逐条获取数据, 使得内存比较稳定. Query的scroll方法得到ScrollableResults.
- 执行效率. 同样可以调整hibernate.jdbc.batch_size参数.
18. 数据批量操作--删除--hibernate3
引入了bulk delete/update操作, 通过一条独立的SQL完成对数据的批量删除/更新. 方法是, 使用hsql创建Query, 再调用Query的executeUpdate()方法.
但是无法解决缓存同步问题(包括一级缓存和二级缓存):