Hibernate之缓存,N+1

来源:互联网 发布:淘宝小号购买实名认证 编辑:程序博客网 时间:2024/06/06 07:17

1,一级缓存,Session级共享
    缓存的作用主要用来提高性能,可以简单的理解成一个Map;使用缓存涉及到三个操作:把数据放入缓存、从缓存中获取数据、删除缓存中的无效数据。
    。save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中,一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用evict,clear方法清除缓存中的内容,且只能在当前session中访问。
    另外,get,load,iterator在查询时会先从一级缓存中拿数据,一般是查询的会拿,save等操作会放不会拿。执行这些操作时,只要打印了sql语句说明与数据库有了交互。
Java模拟缓存:

import java.util.HashMap;import java.util.Map;import cn.itcast.hibernate.domain.User;public class CacheDemo {static Map cache = new HashMap();public static void main(String[] args) {User u = getUser(1);User u1 = getUser(1);}public static void update(User user){updateDB(user);String key = User.class.getName() + user.getId();cache.remove(key);}public static User getUser(int id) {String key = User.class.getName() + id;User user = (User) cache.get(key);if (user != null)return user;user = getFromDB();cache.put(key, user);return user;}private static void updateDB(User user) {}private static User getFromDB() {return null;}}
import java.util.Date;import org.hibernate.Hibernate;import org.hibernate.Query;import org.hibernate.Session;import org.hibernate.stat.Statistics;import cn.itcast.hibernate.domain.Name;import cn.itcast.hibernate.domain.User;public class CacheTest {public static void main(String[] args) {User user = addUser();System.out.println("---------");getUser(user.getId());Statistics st = HibernateUtil.getSessionFactory().getStatistics();System.out.println("put:" + st.getSecondLevelCachePutCount());System.out.println("hit:" + st.getSecondLevelCacheHitCount());System.out.println("miss:" + st.getSecondLevelCacheMissCount());}static User getUser(int id) {Session s = null;User user = null;// HibernateUtil.getSessionFactory().evict(User.class);try {s = HibernateUtil.getSession();Class userClass = User.class;user = (User) s.get(userClass, id);System.out.println(user.getClass());// s.evict(user);// String hql = "from Object";// s.evict(user);// s.clear();// user = (User) s.get(userClass, id);// s.clear();// user = (User) s.load(userClass, id);// Query q = s.createQuery("from User where age>20");// q.setCacheable(true);// user = (User) q.uniqueResult();// System.out.println(user.getName());} finally {if (s != null)s.close();}// try {// s = HibernateUtil.getSession();// Class userClass = User.class;// user = (User) s.get(userClass, id);// System.out.println(user.getName());// } finally {// if (s != null)// s.close();// }return user;}public static User addUser() {User user = new User();user.setBirthday(new Date());Name n = new Name();n.setFirstName("firstName");n.setLastName("lastName");user.setName(n);// 111HibernateUtil.add(user);return user;}}

2,二级缓存,SessionFactory级共享

[2-1]二级缓存是第三方开发的。一级缓存和二级中的(hashtable来实现)缓存是hibernate实现的,但是hibernate实现的不好,所以用的是第三方的。

[2-2]cache.use_second_cache  true打开二级缓存,默认值是true
[2-3]cache.provider_class    hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现。如上代码。

    在hibernate.cfg.xml中加入:<class-cache class="className" usage="read-only"/>或在映射文件的class元素加入子元素:<cache usage="read-write"/>。

    usage:read-only,read-write,nonstrict-read-write,transactional

[2-4]Session的:save(使用这个方法时,配置文件中使用native生成方式的主键,此时这个保存的对象是不缓存的,hilo可以),update,saveOrUpdate,list,iterator,get,load,以及Query,Criteria则都会填充二级缓存,但只有Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询),其他的只是放。    

    Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;所以按照id查询时一般要用get或是load,而不用这俩。修改cache.use_query_cache为true打开对查询的缓存,并且调用query.setCacheable(true)或criteria.setCacheable(true)。

    比如:查询是按照key来查询的,Criteria这种方式查询时,需要让key与缓存map中的key保持一致(,如果不一样,就会查数据库。而且,如果key是对象的id,如果之前sesssion失效了,这时查询id存在,对象不存在,接着遍历id时对象没了,那么就会从数据库找,这样每个id都会找,会出现N+1次查询效率极其低。

(select * from User where userId>20和select * from User where userId>21是不一样的,查询结果是User,但是key里存的是id,然后根据id再在缓存中遍历获取User。当查询出id后,session过期,此时,遍历id,那么缓存是空的,那么就对于每个id都会从数据库中取数据了。)

[2-5]SessionFactory中提供了evictXXX()方法用来清除缓存中的内容。
[2-6]统计信息打开generate_statistics,用sessionFactory.getSatistics()获取统计信息。
3,分布式缓存,中央缓存,使用条件
    分布式缓存:每台机器都有一个缓存,但是如果修改数据库数据后,每台机器上的缓存都要更新,所以查询成本低,但是修改成本高。
    中央缓存:将数据库数据缓存到一台机器上,这样其他机器读取数据时只需要在这台机器上获取即可,但是这样的话,查询成本高,但是修改成本低。
    使用缓存的条件:读取大于修改;数据量不能超过内存容量;对数据要有独享的控制;可以容忍出现无效数据

4,缓存注意项

    flush时将一级缓存与数据库同步,这个我们自己最好不用,因为这个交互是跟数据库交互的,性能开销大,hibernate会自己处理,hibernate已经处理的很好了。hibernate会尽量延迟这个刷新操作,直到最后使用一个批处理操作进行,会尽可能的降低性能开销。

大批处理 
大量操作数据时可能造成内存溢出,解决办法如下:
1.清除session中的数据

for(int i=0;i<100000;i++)session.save(obj);//因为一级缓存是不能控制缓存数量的

//需要根据情况刷新缓存,清除缓存

for(int i=0;i<100000;i++){
session.save(obj);
if(i% 50 == 0){session.flush();session.clear();}
}
2.用StatelessSession接口:它不和一级缓存、二级缓存交互,也不触发任何事件、监听器、拦截器,通过该接口的操作会立刻发送给数据库,与JDBC的功能一样。
StatelessSession s = sessionFactory.openStatelessSession();该接口的方法与Session类似。
3.Query.executeUpdate()执行批量更新,会清除相关联的类二级缓存(sessionFactory.evict(class)),也可能会造成级联,和乐观锁定出现问题[待研究]

5,N+1次查询和懒加载
1.用Query.iterator可能会有N+1次查询。如果一级二级缓存中没数据,用iterator查询时,会先从数据库中查到对应的id,然后根据id,在从数据库中查询,那么这样就产生了n+1条查询语句。使用时,一定要注意。
2.懒加载时获取关联对象。先查从对象,再查主对象,因为对于每个从对象都要查询一次数据库获得主对象,所以此时会出现N+1。因为从对象查完后,主对象并没有查出来,所谓出现N+1的情况,但是反过来,先查主对象,那么就会连带将从对象一并查出来,此时就不会出现N+1的情况。
3.如果打开对查询的缓存。也可能有N+1次查询。见[2-4]
0 0