并发
1. 并发问题
案例:如果用户A查询到某商店的IPHONE剩余100台,然后A要买两个,同时,用户B也查询到库存100台IPHONE,B要买100个,这样就有可能产生并发问题。
2. 悲观锁
悲观锁是数据库的update锁机制[select * from emp for update],不是hibernate锁。悲观锁是在查询上上锁,可以保证A在查询数据的时候,B被阻塞,无法进行查询。
2.1 悲观锁使用
Member member = (Member)session.load(Member.class,1L,LockOptions.UPGRADE);
System.out.println(member);
tips:在我们load方法上,具有锁的参数,我们选择LockOptions.UPGRADE之后,在Load数据的时候,就不会有其它线程进来捣乱了,我们也可以通过Hibernate发送的SQL来观察到,SQL是for update的。
2.2 悲观锁缺陷
悲观锁锁的范围实在是太大了,将整个查询过程都锁住了,很浪费性能,而乐观锁就完美的解决了这种性能问题。
3. 乐观锁
乐观锁的原理就是在数据库表中添加一个版本字段version,初始为0。读取出数据时,将此版本号version一同读出,在提交修改操作之前,再查询一遍数据库中的version,如果和第一次查询到的一致,则予以更新,并将version加1,否则认为是过期数据,由程序控制,重新发送请求。
例:
- A进入数据库,读取一条数据,得到version为0,拿在手里。
- B进入数据库,读取相同数据,得到version为0,拿在手里。
- A修改该条数据,提交前又查询一遍库中version仍为0,则允许修改,version+1,提交。
- B修改该条数据,提交前又查询一遍库中version已经改为1,则不允许修改,此时B手里的数据为过期数据,则由程序控制,重新发送请求。
3.1 乐观锁使用
在实体类中添加一个字段version,必须是private,以免外部调用,否则乐观锁失效。测试的时候,无需setVersion字段,hibernate会自动帮我们进行version的值的注入。
Member.java中添加version字段
private Integer version;
UserHiber.hbm.xml文件中添加标签
<!--version要写在所有property之前-->
<version name="version"></version>
缓存
1. 缓存流程
一条查询请求发出,会先去一级缓存中查询,如果有,直接返回数据,如果没有,再去二级缓存中查询(二级缓存开启的前提下),如果有,直接返回数据,如果还是没有,再去数据库中查询,查询到数据之后,再将这份数据备份到一级缓存和二级缓存中,最后返回数据。
所以说,缓存可以提高效率,但是缓存只能缓存实体对象,不能缓存其他属性。
2. 一级缓存
2.1 一级缓存概念
一级缓存就是session级别的缓存,如果session关闭了,那么一级缓存就被清空,即一级缓存不能跨session存在。
2.2 一级缓存测试
我们在同一个session中做两遍相同的事情,会发现第二次的操作不发SQL,而是从缓存取。
测试get和load方法的缓存机制
Session session = HibernateUtil.getSessionFactory().getSession();
User user1 = (User)session.load(User.class,1L);
user1.setUsername("test");
User user2 = (User)session.load(User.class,1L);
user2.setUsername("test");
session.close();
测试list()方法是否使用缓存
Session session = HibernateUtil.getSessionFactory().getSession();
Query query1 = session.createQuery("from User");
List<User> list1 = query1.list();
for(User user : list1){
System.out.println(user.getUsername());
}
Query query2 = session.createQuery("from User");
List<User> list2 = query2.list();
for(User user : list2){
System.out.println(user.getUsername());
}
session.close();
tips:list方法操作会发送两次sql,说明list方法是不使用一级缓存的,测试的时候需要使用同一个session中的两个不同的query对象,如果是同一个query对象,仍旧走缓存。
2.3 一级缓存同步
在修改持久数据之后,缓存会自动同步。
查询一次数据,然后利用set修改这个数据,再查询一遍相同的数据,数据会自动改么?
Session session = HibernateUtil.getSessionFactory().getSession();
Transaction tx = session.beginTransaction();
try{
User user1 = (User)session.load(User.class,1L);
user1.setUsername("test");// 对持久化对象的改动会直接影响数据库和一级缓存
tx.commit();
User user2 = (User)session.load(User.class,1L);// 再查一次user,这次从缓存中获取
System.out.println(user2.getUsername());// 拿到的是test,说明一级缓存同步更新了
} catch(Exception e){
tx.rollback();
e.printStackTrace();
}
finally{ session.close();
}
2.4 一级缓存移除
我们可以使用session.evict()方法来逐出某个对象,或者直接使用session.clear()来清空一级缓存。
User user1 = (User)session.get(User.class,1L);
session.evict(user1);// 将user1从一级缓存中逐出
User user2 = (User)session.get(User.class,1L);// 继续发送sql语句
System.out.println(user2.getUsername());
3. 二级缓存
二级缓存是sessionFactory级别的,能被所有session共享(可以跨session),如果sessionFactory关闭,那么二级缓存随之消失。
3. 二级缓存
二级缓存是sessionFactory级别的,能被所有session共享(可以跨session),如果sessionFactory关闭,那么二级缓存随之消失。
3.1 二级缓存情景
哪些数据适合使用二级缓存?
- 很少被修改的数据
- 频繁被查询的数据
- 允许偶尔并发的数据
- 不是特别重要的数据
3.2 第三方EHcache
Hibernate自己有二级缓存的技术,但是没有第三方二级缓存技术EHcache好,EHcache支持在内存和硬盘上做缓存,支持查询缓存,而且EHcache缓存策略由自己决定,我们只需要配置即可。
3.2.1 搭建EHcache
在hibernate安装包/project/etc包下,找到ehcache.xml文件,拷贝在工程的classpath下。
添加maven依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.4.3</version>
</dependency>
3.2.2 开启EHcache
[在hibernate.cfg.xml文件中手动开启二级缓存,并指定使用ehcache作为项目的二级缓存技术]
<!-- 设置hibernate开启二级缓存,默认为false -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 指定使用EHcache作为hibernate的二级缓存机制 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
[在对应hbm文件中的中开启EHcache,代表对这个类使用二级缓存]
<class name="com.oracle.pojo.Member" table="t_member">
<cache usage="read-only"/>//开启二级缓存
<id name="t_member_id">
<generator class="native"/>
</id>
<version name="version"></version>
<property name="memberName"/>
</class>
3.2.3 测试EHcache
[对get/load/list和iterator进行跨session测试:注意,此时一定不要跨sessionFactory]
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Session session1 = sessionFactory.openSession();
Member member1 = (Member)session1.load(Member.class,1L);
System.out.println(member1);
session1.close();// 把session1关闭
Session session2 = sessionFactory.openSession();
Member member2 = (Member)session2.load(Member.class,1L);// 不再发送sql,二级缓存生效
System.out.println(member2);
session2.close();
3.2.4 二级缓存策略
二级缓存的策略由中的usage属性来控制,常用值有两个:
1.read-only[只读策略:只能读取缓存中的数据,不能修改缓存中的数据]
2.read-write[读写策略:可以修改缓存中的数据,而且一旦修改数据库数据,那么二级缓存中的对应数据就会同步更新,如果再次查询该数据,不会发送SQL查询]
4. 查询缓存
4.1 特点
查询缓存可以缓存属性,也可以缓存实体对象的ID,一旦查询的数据库的表的数据发生改变,缓存就被清空。
4.2 测试
[查询缓存也需要在cfg文件中设置开启]
<property name="hibernate.cache.use_query_cache">true</property>
[测试:缓存Lang类型属性]
Session session = HibernateUtil.getSessionFactory().openSession();
session.clear();
Query query1 = session.createQuery("select count(*) from Member");
query1.setCacheable(true);
// list方法支持查询缓存,而iterator方法不支持查询缓存
Long num1 = (Long)query1.list().get(0);
System.out.println(num1);
Query query2 = session.createQuery("select count(*) from Member");
query2.setCacheable(true);
Long num2 = (Long)query2.list().get(0);
System.out.println(num2);