1、缓存定义:
1)、缓存介于应用程序和物理数据源之间,缓存中的数据是对数据库中数据的复制,其作用就是降低应用程序对数据源的访问频次,提高应用的运行性能。Hibernate在读取数据的时候,先会在缓存中查询,如果缓存中有相应数据(缓存命中),则直接用里面的数据,避免发送sql到数据库查询的性能消耗。 2)、关键字:缓存----数据库数据复制----放在内存中-----查询时先查缓存----命中直接返回,减少查询消耗。
2、Hibernate缓存分类:
1)、第一种Session缓存:Hibernate内置的缓存机制,不能卸载。该缓存的生命周期依赖于Session对象,Session对象创建则缓存开始,Sessio对象关闭缓存也就随之结束。一般一个Session对应一个事务,所以Session级别的缓存也称为事务缓存,而一个Session对应一个单独的线程,各个线程的Session是不一样的,所以Session缓存也不能共享。 2)、第二种SessionFactory缓存:使用第三方插件,可插拔,又称为应用缓存。这种缓存可以被应用范围内的所有Session共享,这种Session的生命周期随依赖应用的生命周期。 3)、关键字: Session缓存----Hibernate内置----依赖Session生命周期----线程(Session)独有-----不能共享 SessionFactory------第三方提供------应用范围内------所有Session共享
3、Session缓存(同一个Session中):
1)、同一个Session两次get方法查询同一个对象: --------------------------------------------------------------------------------------- //发出查询语句 User user1 = (User) session.get(User.class, 1L); //不会发出查询语句,直接在缓存中查询 User user2 = (User) session.get(User.class, 1L); //验证两次取出的是否是同一个对象 System.out.println("是否是同一对象:"+(user==user2)); 结果打印信息(只有一条查询语句): Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 是否是同一对象:true --------------------------------------------------------------------------------------- 2)、同一个Session两次Iterator查询同一个对象: --------------------------------------------------------------------------------------- //这里会发出查表id的sql语句 Iterator<User> itr1 = session.createQuery("from User").iterate(); while(itr1.hasNext()){ User user = itr1.next(); //这里会根据id查询具体User对象,然后保存在缓存中 System.out.println(user.getUsername()+"--->>>>>>"); } System.out.println("第一次遍历结束"); //发出查表id的sql语句 Iterator<User> itr2 = session.createQuery("from User").iterate(); while(itr2.hasNext()){ User user = itr2.next(); //会先根据id到缓存中找是否有这个User对象,有则直接使用,没有就要数据库中查找 System.out.println(user.getUsername()+"--->>>>>>"); } System.out.println("第二次遍历结束"); 结果打印信息: Hibernate: select user0_.user_id as col_0_0_ from core_user user0_ Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼0--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼1--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼2--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼3--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼4--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼5--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼6--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼7--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼8--->>>>>> Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? 傻逼9--->>>>>> 第一次遍历结束 Hibernate: select user0_.user_id as col_0_0_ from core_user user0_ 傻逼0--->>>>>> 傻逼1--->>>>>> 傻逼2--->>>>>> 傻逼3--->>>>>> 傻逼4--->>>>>> 傻逼5--->>>>>> 傻逼6--->>>>>> 傻逼7--->>>>>> 傻逼8--->>>>>> 傻逼9--->>>>>> 第二次遍历结束 --------------------------------------------------------------------------------------- 3)、第一次iterator查询会发出N+1条sql语句,第一句先查出所有id,然后根据id查询所有实体对象,并保持在Session缓存中,第二次iterator查询先查所有id,然后拿着这些id到Session缓存中查询是否有对应的实体对象,结果是有,所以就不发送sql到数据库了。 4)、Session缓存不缓存属性,只缓存对象。
4、save操作:
1)、save一个对象,但不提交事务: --------------------------------------------------------------------------------------- //开启一个事务 session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); User user = new User(); user.setAge(99); user.setUsername("傻逼99"); //保存对象 此时只分配了一个id给它,还没同步到数据库中 但会把该对象保存在缓存中 Serializable id =session.save(user); User user2 = (User) session.get(User.class, id); System.out.println(user2.getUsername()); 打印信息: Hibernate: select hibernate_sequence.nextval from dual 傻逼99 --------------------------------------------------------------------------------------- 提交事务后: //开启一个事务 session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); User user = new User(); user.setAge(99); user.setUsername("傻逼99"); //保存对象 此时只分配了一个id给它,还没同步到数据库中 但会把该对象保存在缓存中 Serializable id =session.save(user); User user2 = (User) session.get(User.class, id); System.out.println(user2.getUsername()); tc.commit(); 打印信息: Hibernate: select hibernate_sequence.nextval from dual 傻逼99 Hibernate: insert into core_user (user_name, age, user_id) values (?, ?, ?) --------------------------------------------------------------------------------------- 2)、执行save操作会先把对象保存在缓存中,当事务提交或者session.flush时,才会同步到数据库中,所以如果大批量save对象,每save一个对象就放到缓存中,对象数量足够大时,内存肯定溢出,所以可以每保存一定数量的对象就手动清理下缓存,例如: if (i % 20 == 0) { //数据持久化,同步到数据库 session.flush(); //清除缓存的内容 session.clear(); }
5、SessionFactory二级缓存:
1)、使用第三方缓存EHCache,项目中添加encache相关jar包,增加一个ehcache.xml文件放在类路径下: ----------------------------------------------------------------------------------- <!-- 默认缓存配置 --> <defaultCache <!-- 缓存里可以放10000个对象 --> maxElementsInMemory="10000" <!-- 过不过期,如果是true就是永远不过期 --> eternal="false" <!-- 一个对象被访问后多长时间还没有访问就失效(120秒还没有再次访问就失效) --> timeToIdleSeconds="120" <!-- 对象存活时间(120秒),如果设置永不过期,这个就没有必要设了 --> timeToLiveSeconds="120" <!-- 溢出的问题,如果设成true,缓存里超过10000个对象就保存到磁盘里 --> overflowToDisk="true" /> ----------------------------------------------------------------------------------- 也可以单独配置: <!-- 针对某个对象单独配置 --> <cache name="com.sxit.cache.User" maxElementsInMemory="100" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /> ----------------------------------------------------------------------------------- 2)、然后在hibernate.cfg.xml中添加: <!-- 缓存提供商 --> <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <!-- 启用二级缓存 默认是true 可不写 --> <property name="cache.use_second_level_cache">true</property> <!-- 这里可以配置那些实体类需要用到二级缓存 也可在实体类的hbm文件中配置 --> <class-cache usage="read-only" class="com.sxit.cache.User"/> 3)、useage属性表示缓存策略,一般选择read-only,如果这个对象放在缓存中,则不能被修改,如果修改则会报错,所以说,如果是经常要修改的对象,就不用放在缓存中了。使用read-only策略效率好,因为不能改缓存。但是可能会出现脏数据的问题,这个问题解决方法只能依赖缓存的超时,比如上面我们设置了超时为120秒,120后就可以对缓存里对象进行修改,而在120秒之内访问这个对象可能会查询脏数据的问题,因为我们修改对象后数据库里改变了,而缓存却不能改变,这样造成数据不同步,也就是脏数据的问题。 4)、第二种缓存策略read-write,当持久对象发生变化,缓存里就会跟着变化,数据库中也改变了。这种方式需要加解锁,效率要比第一种慢。
6、二级缓存的使用:
1)、两个Session,开启二级缓存查询同一个对象: //开启一个新事务 发起两次相同查询 session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); user = (User) session.get(User.class, 1L); System.out.println(user); tc.commit(); HibernateSessionFactory.closeSession(); session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); user = (User) session.get(User.class, 1L); System.out.println(user); tc.commit(); HibernateSessionFactory.closeSession(); 打印信息: Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? com.sxit.cache.User@1fe571f com.sxit.cache.User@12bf892 2)、两个Session,不开二级缓存查询相同对象: //开启一个新事务 发起两次相同查询 session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); user = (User) session.get(User.class, 1L); System.out.println(user); tc.commit(); HibernateSessionFactory.closeSession(); session = HibernateSessionFactory.getSession(); tc = session.beginTransaction(); user = (User) session.get(User.class, 1L); System.out.println(user); tc.commit(); HibernateSessionFactory.closeSession(); 打印信息: Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? com.sxit.cache.User@1fe571f Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? com.sxit.cache.User@12bf892 3)、一级、二级缓存之间的交互: //禁止一二级缓存交互 这样的话对象只保存在Session缓存中,不会保存到二级缓存中 session.setCacheMode(CacheMode.IGNORE); 打印信息: Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? com.sxit.cache.User@1fe571f Hibernate: select user0_.user_id as user1_0_0_, user0_.user_name as user2_0_0_, user0_.age as age0_0_ from core_user user0_ where user0_.user_id=? com.sxit.cache.User@12bf892 4)、二级缓存不能存放普通属性
7、查询缓存:
1)、一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里,查询缓存还存放查询实体对象的id。 2)、查询缓存的生命周期不确定,当它关联的表发生修改,查询缓存的生命周期就结束。这里表的修改指的是通过hibernate修改,并不是通过数据库客户端软件登陆到数据库上修改。 3)、hibernate的查询缓存默认是关闭的,如果要使用就要到hibernate.cfg.xml文件里配置: <property name="hibernate.cache.use_query_cache">true</property> 并且必须在程序中手动启用查询缓存,在query接口中的setCacheable(true)方法来启用。