Hibernate并发和缓存

本文深入探讨了并发问题及解决方案,包括悲观锁和乐观锁的使用与缺陷,以及一、二级缓存的工作原理和应用,如EHcache的配置与测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发

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,否则认为是过期数据,由程序控制,重新发送请求。

例:

  1. A进入数据库,读取一条数据,得到version为0,拿在手里。
  2. B进入数据库,读取相同数据,得到version为0,拿在手里。
  3. A修改该条数据,提交前又查询一遍库中version仍为0,则允许修改,version+1,提交。
  4. 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 二级缓存情景

哪些数据适合使用二级缓存?

  1. 很少被修改的数据
  2. 频繁被查询的数据
  3. 允许偶尔并发的数据
  4. 不是特别重要的数据

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值