hibernate查漏补缺2
转载请注明: TheViper http://www.cnblogs.com/TheViper
Hibernate对象状态
瞬时(transient):由new操作符创建,且尚未Hibernate Session关联。瞬时对象不会被持久化到数据库,也不会被赋予持久化标识。
持久(persistent):持久化的实例在数据库中有对应的记录,并拥有一个持久化标识。
持久化的实例可能是刚被保存,或刚被加载的,无论哪一种,它都只存在于相关联的Session作用范围内。这点很重要。Hibernate会检测处于持久化状态的对象的任何变动,在当前操作单元执行完毕时,将对对象数据与数据库同步。
脱管(detached):在与持久化对象关联的Session关闭后,对象就变成脱管状态。
Hibernate的第一级缓存(Session缓存)
当调用Session的save(),update(),saveOrUpdate(),load()或get()方法,以及调用Query接口的list(),iterator()或filter()方法时,如果缓存中没有相应的对象,Hibernate便会将对象添加到Session缓存中。
比如:
Transaction transaction = session.beginTransaction(); Feeling feeling = (Feeling) session.get(Feeling.class, 1); System.out.println(feeling); System.out.println(feeling.getFeelingComments()); Feeling feeling1 = (Feeling) session.get(Feeling.class, 1); transaction.commit();
Hibernate:
select
feeling0_.feeling_id as feeling1_1_0_,
feeling0_.content as content1_0_,
feeling0_.id as id1_0_
from
feeling feeling0_
where
feeling0_.feeling_id =?
model.Feeling@e34201
Hibernate:
select
feelingcom0_.feeling_id as feeling3_1_1_,
feelingcom0_.feelingComment_id as feelingC1_1_,
feelingcom0_.feelingComment_id as feelingC1_2_0_,
feelingcom0_.content as content2_0_,
feelingcom0_.feeling_id as feeling3_2_0_,
feelingcom0_.id as id2_0_
from
feeling_comment feelingcom0_
where
feelingcom0_.feeling_id=?
[model.FeelingComment@39c71a, model.FeelingComment@11e8b08, model.FeelingComment@12fe3f3, model.FeelingComment@e91824]
可以看到第二次的get()并没有生成sql.
可以使用1.evict()从缓存中清除指定的持久化对象。2.clear()清除所有。。。。多数情况下,不提倡通过上面两个方法管理缓存。
当Session缓存中对象的属性发生变化时,Session并不会立即清理缓存并执行相关的sql,使数据库同步,而只是在特点的时间点才清理缓存。这使得Session可以把几条相关的sql合并成一条,提升性能。
上面的特点的时间点包括:
1.调用Transaction的commit()方法。这时清理缓存,然后向数据库提交事务。这样做是为了最大限度减少数据库的访问和尽可能的缩短当前事务对数据库中相关资源的锁定时间。
2.执行一些查询操作。这样做是为了保证查询返回的结果是即时,正确的。
3.显式调用flush().注意,flush()不会提交事务,数据还有可能rollback.
对于flush()最常用的用法当然是批量操作了。比如批量插入100000条数据。
for(int i=1;i<100000;i++){ Customer c=new Customer(); session.save(c); if(i%20==0){ session.flush(); session.clear(); } }
设置hibernate.jdbc.batch_size=20。一定数量(20)时强制刷出,使缓存与数据库同步(数据写入数据库),然后清空所有缓存,避免内存溢出。
list()和iterator()
上面说了这两个都会把结果添加到Session缓存。下面是不同点。
1.对待缓存的方式不一样:如果配置了查询缓存的话,list()会先去查询缓存里面找,没找到去数据库找。注意,list()不会从Session缓存中找,也就是说下面的代码并没有减少sql语句。
Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); Feeling feeling1 = (Feeling) session.load(Feeling.class, 1); System.out.println(feeling1); Query q = session .createQuery("from Feeling feeling where feeling.feeling_id=1"); List<Feeling> feeling = (List<Feeling>) q.list(); System.out.println(feeling); transaction.commit(); session.close();
可以看到通过load()使feeling_id=1的Feeling对象被添加到了Session缓存。生成的sql.
Hibernate:
select
feeling0_.feeling_id as feeling1_1_0_,
feeling0_.content as content1_0_,
feeling0_.id as id1_0_
from
feeling feeling0_
where
feeling0_.feeling_id =?
model.Feeling@1728f89
Hibernate:
select
feeling0_.feeling_id as feeling1_1_,
feeling0_.content as content1_,
feeling0_.id as id1_
from
feeling feeling0_
where
feeling0_.feeling_id =1
[model.Feeling@1728f89]
而iterate()是先从数据库中查询出所有满足条件的数据索引,然后再根据这些数据索引进入一级和二级缓存进行匹配,如果对于数据索引有实体对象则直接返回该对象,如果没有则在具体使用对象的时候才会进入数据库加载数据,并且把数据索引和对于实体对象放进缓存。什么意思呢?看下面的代码.
Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); Query q = session .createQuery("from Feeling feeling where feeling.feeling_id=1"); Iterator<Feeling> feeling = (Iterator<Feeling>) q.iterate(); System.out.println(feeling.next()); transaction.commit(); session.close();
Hibernate:
select
feeling0_.feeling_id as col_0_0_
from
feeling feeling0_
where
feeling0_.feeling_id =1
Hibernate:
select
feeling0_.feeling_id as feeling1_1_0_,
feeling0_.content as content1_0_,
feeling0_.id as id1_0_
from
feeling feeling0_
where
feeling0_.feeling_id =?
model.Feeling@11e3986
可以看到明明就是一个很简单的查询,iterate()都要把一句语句变成两句 ,再看下面有Session缓存匹配的情况。
Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); Feeling feeling1 = (Feeling) session.load(Feeling.class, 1); System.out.println(feeling1); Query q = session .createQuery("from Feeling feeling where feeling.feeling_id=1"); Iterator<Feeling> feeling = (Iterator<Feeling>) q.iterate(); System.out.println(feeling.next()); transaction.commit(); session.close();
Hibernate:
select
feeling0_.feeling_id as feeling1_1_0_,
feeling0_.content as content1_0_,
feeling0_.id as id1_0_
from
feeling feeling0_
where
feeling0_.feeling_id =?
model.Feeling@fcabd6
Hibernate:
select
feeling0_.feeling_id as col_0_0_
from
feeling feeling0_
where
feeling0_.feeling_id =1
model.Feeling@fcabd6
可以看到这次iterate()在查询过数据索引后就没有发出sql,而是直接用的上面的缓存。当然如果只是查询某几个字段,是不会去缓存中匹配的(默认设置)。
2.发出sql不一样。很显然,iterate()会产生N+1问题。
既然这样,那iterate()适用于什么情况。
- 查找对象包含大量字段
- 在二级缓存中有大量的匹配对象
如果满足这两点,就可以用类似
select
feeling0_.feeling_id as col_0_0_
from
feeling feeling0_
where
feeling0_.feeling_id =1这种只查询了一个字段的语句查到对象的所有字段。
另外,iterate()返回的Itarator对象一直处于打开状态,在以下情况被关闭。
- 遍历访问完结果集中的所有对象
- 关闭Session
- 通过org.hibernate.Hibernate.close(iterator)方法关闭Itarator对象
load()和get()
http://www.cnblogs.com/binjoo/articles/1621254.html
注意两者的运行流程:get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。
对load(),如果只是Feeling feeling = (Feeling) session.load(Feeling.class, 1);是不会发出sql的,只有像System.out.println(feeling);这样使用了feeling才会发出。
load()只是生成类的动态代理,并没有将数据库的数据加载到相应的类中,所以适合在数据更新或为表插入外键数据时使用,
如hibernate查漏补缺1中的那个好友分类例子中,插入新加的好友并把他加入到已经存在的分类中。
Friend_Category fc = new Friend_Category(); fc.setCategory_name("已经存在的好友分类"); User_Friend_pk pk = new User_Friend_pk(); pk.setFriend_name(“新好友”); pk.setName("TheViper"); User_Friend uf = new User_Friend(); uf.setUser_friend_pk(pk); Friend_Category fc1 = (Friend_Category) session.load( Friend_Category.class, 1); uf.setFriend_categorys(fc1); // uf.setSort("好基友"); session.saveOrUpdate(fc1); session.save(uf);
注意,这时对load的Friend_Category要用saveOrUpdate(),否则会出现异常 org.hibernate.PersistentObjectException: uninitialized proxy passed to save()。
很多人都在用Spring的HibernateTemplate模板。它有个不好的地方,比如:
Feeling f = hibernateTemplate.load(Feeling.class, 1); System.out.println(f);
返回异常 org.hibernate.LazyInitializationException: could not initialize proxy - no Session
原因在于HibernateTemplate和Spring的其他dao模板如JdbcTemplate一样,走的是下面的流程:
1.准备资源->2.启动->3.数据访问->4.返回数据->5.提交/回滚事务->6.处理异常关闭资源
其中的3,4是我们写的,其他的都是模板帮我们做的。也就是说,每次用了HibernateTemplate的方法后,HibernateTemplate都会自己关闭资源,防止数据库连接,内存泄漏,而关闭的资源中就包括了Hibernate的Session.Session,自然就找不到Session缓存和类的动态代理(load()返回的)了。
或许是因为这个原因,在hibernate4中,与Spring集成时,Spring就没有提供相应的模板了。
查询缓存
如果启用,当第一次执行查询语句时,hibernate会把查询的结果存放在二级缓存中。注意,如果查询结果中有实体类,则查询缓存只会保存实体类的id.比如,
Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); Query q = session .createQuery("from Feeling feeling"); q.setCacheable(true); List<Feeling> feeling1 = (List<Feeling>) q.list(); System.out.println(feeling1); transaction.commit(); session.close(); Session session1 = sf.openSession(); Transaction transaction1 = session1.beginTransaction(); Query q1 = session1 .createQuery("from Feeling feeling"); q1.setCacheable(true); List<Feeling> feeling = (List<Feeling>) q1.list(); System.out.println(feeling); transaction1.commit(); session1.close();
Hibernate: select feeling0_.feeling_id as feeling1_1_, feeling0_.content as content1_, feeling0_.id as id1_ from feeling feeling0_ [model.Feeling@114b355, model.Feeling@113925a, model.Feeling@1ed5550] Hibernate: select feeling0_.feeling_id as feeling1_1_0_, feeling0_.content as content1_0_, feeling0_.id as id1_0_ from feeling feeling0_ where feeling0_.feeling_id =? Hibernate: select feeling0_.feeling_id as feeling1_1_0_, feeling0_.content as content1_0_, feeling0_.id as id1_0_ from feeling feeling0_ where feeling0_.feeling_id =? Hibernate: select feeling0_.feeling_id as feeling1_1_0_, feeling0_.content as content1_0_, feeling0_.id as id1_0_ from feeling feeling0_ where feeling0_.feeling_id =? [model.Feeling@1000db5, model.Feeling@1f28c, model.Feeling@1c64fff]
这里我没有对Feeling的映射文件设置类的二级缓存,可以看到第二次是把保存的feeling_id取出进行查询的,而不是缓存了实体类的所有属性。
如果把Feeling类的二级缓存打开,就能看到第二次查询不会有sql出现.说明查询缓存如果要使用,应该与二级缓存一起使用。
Hibernate:
select
feeling0_.feeling_id as feeling1_1_,
feeling0_.content as content1_,
feeling0_.id as id1_
from
feeling feeling0_
[model.Feeling@80da83, model.Feeling@1520c38, model.Feeling@1a05d51]
[model.Feeling@176d5d3, model.Feeling@16d471b, model.Feeling@1dee8d0]
还有一点,只配置了hibernate.cache.use_query_cache=true的话,还是不会用查询缓存。必须在调用Query接口时query.setCacheable(true);.
Cascade
在<set>,<one-to-one>,<many-to-one>(个人觉得在many-to-one里面很没用)中使用。
Hibernate日志
Hibernate第二级缓存
进程或集群范围内的缓存,SessionFactory级别,而第一级缓存是事务范围的缓存。第二级缓存中存放的是对象的散装数据。
第二级缓存的4种并发访问策略,每一种策略对应一种事务隔离级别。
- 事务型(transactional):仅在受管理的环境中适应。提供repeatable read事务隔离级别,适用于经常访问但很少被修改的数据,可防止脏读和不可重复读这类并发问题。
- 读写型(read-write):read committed事务隔离级别,仅在非集群的环境中适用。适用于经常访问但很少被修改的数据,防止脏读。
- 非严格读写型(nonstrict-read-write):不保证缓存与数据库中数据的一致性。如果两个事务访问缓存中的相同数据,则必须为该数据一个很短的数据过期时间,以避免脏读。适用于极少被修改且允许偶尔脏读的数据这种情况。
- 只读型(read-only):适用于从来不会修改的数据。
transactional隔离级别最高,并发性能最低;read-only则反之。
缓存插件可分为进程范围内缓存(EHCache,OSCache),集群范围内缓存(SwarmCache,JbossCache).其中SwarmCache不支持查询缓存。
配置hibernate.cfg.xml
<property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
Hibernate允许在类和集合上配置二级缓存,在<class>和<set>上都有<cache>子元素.
用法:<cache usage="transactional|read-write|nonstrict-read-write|read-only" region="regionName" include="all|non-lazy"/>
usage:指定并发访问策略。region:指定二级缓存的区域名字,默认为类或集合的名字。include:指定当缓存一个对象时,会不会缓存它的映射为延迟加载的属性。默认all.
ehcache配置
配置<cache>的时候有一点要注意,在<set>下配置了<cache>的话还需要在<set>的另一边的<class>下也配置<cache>才会让二级缓存对集合有效。
管理二级缓存
sessionFactory.evict(Category.class,new Long(1));//清除二级缓存中id为1的Category对象
sessionFactory.evict(”mypack.Category“);//清除二级缓存中Category类的所有对象
sessionFactory.evictCollection(”mypack.Category.items“);//清除二级缓存中Category类的所有对象的items集合
Session与二级缓存的交互模式:
用org.hibernate.CacheMode的5个静态常量表示。
- CacheMode.NORMAL:正常模式(默认),Session会向二级缓存中读取和写入数据。
- CacheMode.IGNORE:忽略模式,Session不会向二级缓存读取,也不会写入数据。
- CacheMode.GET:Session对二级缓存只读不写。
- CacheMode.PUT:Session对二级缓存只写不对。
- CacheMode.REFRESH:刷新模式,Session不会从二级缓存中读取数据,但会向其中写入从数据库读取的数据。和PUT的区别在于REFRESH会忽略配置文件中的hibernate.cache.use_minimal_puts属性,强制刷新二级缓存中的所有数据。
用法:session.setCacheMode(CacheMode.IGNORE);