求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
hibernate查询与缓存
 

作者:yaerfeng,发布于2012-4-25

 

Hibernate查询

首先介绍get()和load()方法的区别:

get()方法和load()方法的区别主要在于对二级缓存的使用上。

load()方法会使用二级缓存,而get()方法在一级缓存没有找到会直接查询数据库,不会去二级缓存中查找。

get():如果在数据库中没有记录会返回空,get()无论如何都会返回数据.

load():如果数据库中没有记录会抛出异常,如果有数据返回的是一个代理对象。

get()方法默认不支持lazy(延迟加载)功能,而load支持延迟加载

get()方法在查询不到数据时,返回null,而load因为支持延迟加载,只有在使用对象时才加载,所以如果数据库中不在数据load会抛出异常(org.hibernate.ObjectNotFoundException)。

get()和load()只根据主键查询,不能根据其它字段查询,如果想根据非主键查询,可以使用HQL

list和iterator()方法之间的区别:(N+1?)

list()方法在执行时,直接运行查询结果所需要的查询语句。

iterator()方法则是先执行得到对象ID的查询,然后在根据每个ID值去取得所要查询的对象。

因此:对于list()方式的查询通常只会执行一个SQL语句,而对于iterator()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数).

结果集的处理方法不同:

list()方法会一次活的所有的结果集对象,而且他会依据查询的结果初始化所有的结果集对象。如果在结果集非常庞大的时候会占据非常多的内存,甚至会造成内存溢出的情况发生。

iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。一次在访问中可以控制缓存中对象的数量,以避免占用过多的缓存,导致内存溢出情况的发生。

HQL:HQL是一种面向对象的查询语言,HQL的操作对象是类、实例和属性等。

SQL:sql的操作对象是数据表和列等数据对象。

Hql是完全面向对象的查询语言,因此可以支持继承和多条等特征。

HQL查询依赖于Query类,每个Query实例对应一个查询对象。

定参数的功能,Query 接口才是真正的HQL查询接口。

//创建一个Query 对象

Java代码

Query query = session.createQuery ("from Customer as c where c.name=:customerName and c.age=:customerAge");

//动态绑定参数

Java代码

query.setString("customerName","Tom");

query.setInteger("customerAge",21);

//执行查询语句,返回结果

Java代码

List result = query.list();

HQL查询步骤:

1:获取Hibernate Session对象。

2:编写HQL语句。

3:以HQL语句作为参数,调用Session的createQuery方法创建查询对象。

4:如果HQL语句包含参数,调用Query的setXXX()方法为参数赋值。

5: 调用Query对象的list等方法遍历查询结果。

Query还包含两个方法:

Java代码

setFirstResult(int firstResult)://设置返回的结果集从第几条记录开始。

setMaxResults(int maxResults)://设置本次查询返回的结果数。

实体的删除和更新。

投影查询:只查询属性的一部分。

查询一个属性返回的是字符串

查询二个字段返回的是数组

动态构造查询:主要用于几十个表查询;

要构建一个新的对象,要加上一个构造函数;

在new对象的时候要加上包名

不要使用count(*) 要count(持久化对象)

分组与排序:

Order by子句可以通过asc或desc关键字排序

如:

Java代码

form User u Order by u.name asc,u.age desc;

Group by子句与统计查询:

如:

Java代码

String hql = "select count(u),u.age from User u group by u.age having count(u)>10";

List list = session.createQuery(hql).list();

标准的SQL聚合函数都可以在HQL语句中使用,比如:count(),sum(),max(),min(),age()等

连接查询:

内连接:inner join

左外连接:left outer join

右外连接:right outer join

全连接:full join(不常用)

迫切外连接:left outer join fetch,left join

fetch:用于一次性获取连接数据,特别是集合数据。减少与数据库交互的次数。

left out join:使用做外连接位于left join 左侧表的所有记录及对应的order类的记录信息都将显示出来。

right out join:与 left out join 正好相反,right out join 返回的是HQL右侧表中的所有记录以及对应的Customer对象记录信息。

获取集合数据的四种方式:

1:Hibernate.initialize(user.getOrder());

2:user.getOrder().size();

3:左右迫切连接

4:高级过滤器

条件查询语句(Criteria Queries):利用对象进行对象查询。

主要的接口有:Criteria、Criterion和expression_r和Restrictions类组成。能够支持在运行时动态生成SQL语句。

条件查询步骤:

1:通过seesion的CreateCriteria()方法,创建一个Criteria对象

2:设置查询对象,name指对象的属性

3:把查询条件添加到Criteria对象中

4:执行list()查询返回结果。

条件查询通过三个类完成:

Criteria:代表一次查询.

Criterion:代表一个查询条件.

Restrictions:产生查询条件的工具类.

本地sql查询:使用手写的SQL来完成所有的create\update\delete操作也包括存储过程

本地sql步骤:

1:通过session创建一个SqlQuery对象。

2:编写sql语句

3:以SQL语句作为参数,调用Session的createSQLQuery方法创建查询对象

4:如果SQL语句包含参数,则调用Query的setXX方法为参数赋值

5:调用SQLQuery对象的addEntity或addScalar方法,将选出的结果与实体或标量值关联.

如:HQL的sql

Java代码

String sql = "select c.c_id as {c.id},c.c_name as {c.name} from CUSTOMER c where c.id = 1";

注意:如果使用*,表的别名和类的别名要一致

Java代码

String sql1 = "select {c.*} from CUSTOMER c where c.c_id=2 ";

如:条件查询的sql

Java代码

SQLQuery query = session.createSQLQuery(sql);

query.addEntiy("c",Customer.class);

多态查询:

多态查询是指可以查询到当前类及所有子类的实例,HQL和条件查询都支持多态查询。

如:hql语句查询出所有的持久化对象

Java代码

Query query = session.createQuery("from java.lang.object");

System.out.println(query.list());

HQL支持多态查询,多态查询是指查询出当前类及所有子类的实例,

session.createQuery("from Employee"); or session.createCriteria(Employee.class);

如果Employee类有两个子类:HourlyEmployee和SalariedEmployee,那么这个查询语句会查出所有的Employee实例,以及HourlyEmployee类和SalariedEmployee类的实例。

Java代码

session.createQuery("from HourlyEmployee");

session.createCriteria(HourlyEmployee.class);//只检索子类

查询结果集排序:

Hql与条件查询都支持查询结果集排序,只是HQL采用Order by关键字,而条件查询采用order类进行查询结果排序。

分页查询:

分页查询是数据库应用中常见的处理方式,Query和Criteia接口都提供了用于分页查询的方法.

Java代码

setFistResult(int): //指定从哪一个对象开始查询,参数是索引位置从0开始。

setMaxResults(int): //指定一次最多查询的对象数量。

子查询:

1:查询有一个订单以上的客户。

如:

Java代码

from Customer c where 1<(select count(o) from c.orders o)

hql子查询依赖于底层数据库 对子查询支持的能力。

所有的子查询可以使用连接查询和分组查询语句来代替。

如:

Java代码

select c from Customer c join c.orders o group by c.id having count(o)>1

如果子查询返回多条记录,可以使用关键字量化:

1:all 2:any 3:some 4:in 5:exists

如:

Java代码

//返回所有订单价格小于100的客户

from Customer c where 100>all(select o.price form c.orders o)

//返回有一条订单的价格小于100的客户

from Customer c where 100>any(select o.price from c.orders o)

//返回有一条订单的价格等于100的客户

from Customer c where 100=some(select o.price from c.order o)

//返回有一条订单的价格等于100的客户

from Customer c where 100 = any (select o.prive from c.orders o)

//返回有一条订单的价格等于100的客户

from Customer c where 100 in (select o.price from c.orders o)

参数绑定:

1:传统的JDBC的参数绑定

如:

Java代码

PrepareStatement pre = connection.prepare("select * from User where user.name = ?");

pre.setString(1,"Zhao");

ResultSet rs = pre.executeQuery();

Hibernate参数绑定:在hibernate中有4种参数绑定方式

1:按参数名称(命名参数)绑定,在语句中定义命名参数要用":"开头

2:按参数位置绑定

3:Hql查询中可以通过setParameter()方法绑定任意类型的参数

4:setProperties()方法,将命名参数与一个对象的属性值绑定在一起

如:参数":"开头

Java代码

Query query = session.createQuery("from User user where user.name = customername and user.age = :customerage");

query.setString("customername",name);

query.setInteger("customerage",age);

如:参数位置绑定

Java代码

Query query = session.createQuery("from User user where user.name =? and user.age = ?");

query.setString(0,name);

query.setInteger(1,age);

如:setParameter他有三个参数一般写两个参数

Java代码

String hql = "from User user where user.name = :customername";

Query query = session.createQuery(hql);

query.setParameter("customername",name,Hibernate.STRING);

如:setProperties

Java代码

Customer customer = new Customer();

customer.setName("sdsd");

customer.setAge(99);

Query query = session.createQuery("from Customer c where c.name=:name and c.age = :age");

query.setProperties(customer);

定义命名查询:

命名的SQL语句不是放在程序中,而是放在配置文件中,这种方式是以松耦合的方式配置SQL语句,可以提高程序解耦.

在Customer.hbm.xml配置文件中

Java代码

<query name="findCustomer">

<!CDAATA[from Customer c where c.name = :name] />

</query>

程序中的代码:

Java代码

Query query = session.getNamedQuery("findCustomer");

query.setString("name","tiger");

List list = query.list();

命名的SQL语句:

Java代码

<sql-query name="mysqlquery">

<!-- 关联返回的结果与实体类 -->

<return alias="s" class="com.lovo.po.Customer">

<!-- 定义命名SQL查询的SQL语句 -->

select {c.*}

from Customer Customer c where c.name like "刘德华"

</sql-query>

sql_query是hibernate-mapping元素的子元素,因此可以直接通过session访问

缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,

目的是为了减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能.

Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候 就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应 用程序对物理数据源的访问,使得程序的运行性能明显的提升.

缓存分两级,一级session缓存,就是常说的一级缓存;二级应用缓存(二级缓存);

一级缓存,一级缓存依赖于session,在一个session中就是一个缓存,当session失效时,缓存消失。

/**两个session两次加载**/

Java代码

public void loadBookAgain(){

Session session = HibernateSessionFactory.getSession();

Book book1 = (Book) session.get(Book.class, 6);

Book book2 = (Book) session.get(Book.class, 6);

session.close();

// Session session1 = HibernateSessionFactory.getSession();

// Book book2 = (Book) session1.get(Book.class, 6);

// session1.close();

}

在一个session里面查询两次相同的book,只会执行一次sql。

但若放在不同的session中,将会执行两次数据库查询。

解决问题的办法就是用二级缓存。

二级缓存是SessionFactory级别的全局缓存,它底下可以使用不同的缓存类库,比如ehcache、oscache等。

不是所有的数据都适合放在二级缓存中

下面这几种情况就不适合加载到二级缓存中:

1.经常被修改的数据

2.绝对不允许出现并发访问的数据

3.与其他应用共享的数据

下面这己种情况合适加载到二级缓存中:

1.数据更新频率低

2.允许偶尔出现并发问题的非重要数据

3.不会被并发访问的数据

4.常量数据

5.不会被第三方修改的数据

配置二级缓存比较简单,以ehcache为例:

添加缓存文件ehcache-hibernate-local.xml

Java代码

<?xml version="1.0" encoding="UTF-8"?>

<ehcache>

<diskStore path="java.io.tmpdir/hibernate/book" />

<defaultCache maxElementsInMemory="10000" overflowToDisk="true" eternal="false"

memoryStoreEvictionPolicy="LRU" maxElementsOnDisk="10000000"

diskExpiryThreadIntervalSeconds="600"

timeToIdleSeconds="3600" timeToLiveSeconds="100000" diskPersistent="false" />

<!-- Special objects setting. -->

<cache name="bean.entity.Book" maxElementsInMemory="500" overflowToDisk="true"

eternal="true">

</cache>

</ehcache>

maxElementsInMemory为缓存对象的最大数目,

eternal设置是否永远不过期,

timeToIdleSeconds对象处于空闲状态的最多秒数,

timeToLiveSeconds对象处于缓存状态的最多秒数 。

在实体bean的hbm.xml文件中加上缓存配置:

Java代码

<!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write

nonstrict-read-write transactional-->

<cache usage="read-write" />

现在大部分的hibernate应用不再写实体映射配置文件,那么就在实体bean中加上

//默认的缓存策略.

Java代码

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

在hibernate定义sessionFactory中加上查询缓存配置:

<!-- 设置二级缓存插件EHCache的Provider类-->

Java代码

<property name="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider

</property>

<!-- 启动"查询缓存" -->

Java代码

<property name="hibernate.cache.use_query_cache">true</property>

<property

name="hibernate.cache.provider_configuration_file_resource_path">

/ehcache-hibernate-local.xml

</property>

如果项目试用了spring,那么相应配置为:

Java代码

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">${hibernate.dialect}</prop>

<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>

<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>

<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>

<prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache-hibernate-local.xml</prop>

</props>

</property>

两种配置基本一致。

这个时候在按实体的ID来查询的时候即使不在一个session中,hibernate也只是执行一次sql。

查询缓存做到现在,有一点效果,但基本还是个摆设。

看下面代码:

Java代码

public void listBookTwice(){

String hql = "from Book";

Session session = HibernateSessionFactory.getSession();

Query q = session.createQuery(hql);

List<Book> list1 = q.list();

List<Book> list2 = q.list();

session.close();

同一个query。list了两次,按照之前的效果,应该是执行一个sql。事实是,他要去查两次,

在一个query尚且如此,两个不用说,肯定也是没有用到缓存了。

难道缓存失效了?呵呵,其实是因为我们虽然配置了缓存,但是在query级却没有设置缓存,如果需要query缓存,

则需要手工写入:

q.setCacheable(true);来激活查询缓存。

修改代码如下:

Java代码

public void listBookTwice(){

String hql = "from Book";

Session session = HibernateSessionFactory.getSession();

Query q = session.createQuery(hql);

q.setCacheable(true);

List<Book> list1 = q.list();

for(Book b : list1){

System.out.println(b.getBname()+"--------->list1");

}

// List<Book> list2 = q.list();

session.close();

Session session2 = HibernateSessionFactory.getSession();

Query q2 = session2.createQuery(hql);

q2.setCacheable(true);

List<Book> list2 = q2.list();

for(Book b : list2){

System.out.println(b.getBname()+"--------->list2");

}

session2.close();

}

在两个session立分别list查询,ok,只输出一条sql。说明二级缓存在list查询的时候也起作用了。

那hibernate是根据什么来缓存list呢,

对于查询缓存来说,缓存的key是根据hql生成的sql,再加上参数,分页等信息(可以通过日志输出看到,不过它的输出不是很可读,最好改一下它的代码)。

比如hql:

from Cat c where c.name like ?

生成大致如下的sql:

select * from cat c where c.name like ?

参数是"tiger%",那么查询缓存的key*大约*是这样的字符串(我是凭记忆写的,并不精确,不过看了也该明白了):

select * from cat c where c.name like ? , parameter:tiger%

这样,保证了同样的查询、同样的参数等条件下具有一样的key。

现在说说缓存的value,如果是list方式的话,value在这里并不是整个结果集,而是查询出来的这一串ID。

也就是说,不管是list方法还是iterate方法,第一次查询的时候,它们的查询方式很它们平时的方式是一样的,

list执行一条sql,iterate执行1+N条,多出来的行为是它们填充了缓存。但是到同样条件第二次查询的时候,就都和iterate的行为一样了, 根据缓存的key去缓存里面查到了value,value是一串id,然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。

可以看出来,查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候,都是既填充查询缓存又填充class缓存的。

这里还有一个很容易被忽视的重要问题,即打开查询缓存以后,即使是list方法也可能遇到1+N的问题!相同条件第一次list的时候, 因为查询缓存中找不到,不管class缓存是否存在数据,总是发送一条sql语句到数据库获取全部数据,然后填充查询缓存和class缓存。

但是第二次执行的时候,问题就来了,如果你的class缓存的超时时间比较短,现在class缓存都超时了,但是查询缓存还在,
那么list方法在获取id串以后,将会一个一个去数据库load!因此,class缓存的超时时间一定不能短于查询缓存设置的超时时间
!如果还设置了发呆时间的话,保证class缓存的发呆时间也大于查询的缓存的生存时间。这里还有其他情况,比如class缓存被程序强制evict了,

这种情况就请自己注意了。

另外,如果hql查询包含select字句,那么查询缓存里面的value就是整个结果集了。

可以理解为hibernate缓存了每次查询的hql语句作为缓存map的key,将对应对象的id作为value缓存,每次遇到相同的hql,就将id取出来, 如果在缓存里面有对象,就从缓存取,没有的话就去数据库load

缓存什么时候更新呢?

看代码:

Java代码

public void update(){

Session session = HibernateSessionFactory.getSession();

Transaction tran = session.beginTransaction();

tran.begin();

Book b = (Book) session.get(Book.class, 7);

b.setIsbn("567890");

session.saveOrUpdate(b);

tran.commit();

session.close();

}

public void listBookTwice(){

String hql = "from Book";

Session session = HibernateSessionFactory.getSession();

Query q = session.createQuery(hql);

q.setCacheable(true);

List<Book> list1 = q.list();

for(Book b : list1){

System.out.println(b.getBname()+"----"+b.getIsbn()+"--------->list1");

}

session.close();

}

public void listBookAgain(){

String hql = "from Book";

Session session2 = HibernateSessionFactory.getSession();

Query q2 = session2.createQuery(hql);

q2.setCacheable(true);

List<Book> list2 = q2.list();

for(Book b : list2){

System.out.println(b.getBname()+"----"+b.getIsbn()+"--------->list3");

}

session2.close();

}

public static void main(String[] args) {

BookDao dao = new BookDao();

dao.listBookTwice();

dao.update();

dao.listBookAgain();

}

先list一次,之间更新一个book,第二次list,输出:

Java代码

Hibernate: select book0_.id as id0_, book0_.bname as bname0_, book0_.isbn as isbn0_, book0_.price as price0_ from hibernate_test.dbo.book book0_

book1250672666171----123456--------->list1

book1250672666203----123456--------->list1

book1250672666203----123456--------->list1

book1250672666203----123456--------->list1

Hibernate: update hibernate_test.dbo.book set bname=?, isbn=?, price=? where id=?

Hibernate: select book0_.id as id0_, book0_.bname as bname0_, book0_.isbn as isbn0_, book0_.price as price0_ from hibernate_test.dbo.book book0_

book1250672666171----123456--------->list3

book1250672666203----567890--------->list3

book1250672666203----123456--------->list3

book1250672666203----123456--------->list3

book1250672666203----123456--------->list3

可见,当数据库有更新的时候,缓存就失效了。


相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

 
分享到
 
 
     


Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...