Hibernate中的Session的refresh()方法执行效果与数据库的隔离级别有关

本文探讨了Hibernate的Session刷新方法refresh()如何受数据库隔离级别影响。当数据库更新后,refresh()可能会因隔离级别导致查询结果不变。文章详细介绍了数据库事务的隔离性,包括脏读、不可重复读和幻读等并发问题,以及Oracle和MySQL的隔离级别设置。同时,讲解了在Hibernate中设置事务隔离级别的方法,强调了在Hibernate配置文件中设置hibernate.connection.isolation属性的重要性。

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

//4. 执行保存操作
News news = (News) session.get(News.class, 4); 
System.out.println(news);//第一个输出News [id=4, title=4Java, author=地方, date=2017-02-18]
//打断点,执行完第一个输出后,在数据库端把记录的author字段值改为sun,后继续执行代码		
session.refresh(news);
System.out.println(news);//第二个输出News [id=4, title=4Java, author=地方, date=2017-02-18]
//可以看到没有任何改变。
		
//5. 提交事务 
transaction.commit();

refresh(): 会强制发送 SELECT 语句, 以使 Session 缓存中对象的状态和数据表中对应的记录保持一致!

但是为什么明明改了数据库,执行refresh后Hibernate的确发出了查询语句,但结果不变

Hibernate: 
    select
        news0_.ID as ID1_0_0_,
        news0_.TITLE as TITLE2_0_0_,
        news0_.AUTHOR as AUTHOR3_0_0_,
        news0_.DATE as DATE4_0_0_,
        news0_.CONTENT as CONTENT5_0_0_,
        news0_.PICTURE as PICTURE6_0_0_,
        (SELECT
            concat(news0_.title,
            ',',
            news0_.author) 
        FROM
            NEWS n 
        WHERE
            n.id = news0_.id) as formula0_0_ 
    from
        NEWS news0_ 
    where
        news0_.ID=?
原因和数据库的隔离级别有关。我使用的是mysql5.7。

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.


数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题. 
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱


数据库提供的 4 种事务隔离级别:


Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED 
Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ


我们可以在mysql中设置隔离级别,也可以在Hibernate中设置隔离级别:

mysql中:

每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
查看当前的隔离级别: SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:  
set transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
  set global transaction isolation level read committed;

Hibernate中:

JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
1. READ UNCOMMITED
2. READ COMMITED
4. REPEATABLE READ
8. SERIALIZEABLE
Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别

所以我们在Hibernate.cfg.xml中:

<!-- 设置数据库的隔离级别 -->
		<property name="connection.isolation">2</property>


<think>好的,用户的问题是关于多线程环境下事务提交后如何正确查询数据以确保数据一致性。首先,我需要理解用户的具体需求。他们可能是在开发高并发应用时,遇到了事务提交后数据不一致的情况,比如一个线程提交了事务,但另一个线程查询时看不到更新后的数据。这可能涉及到数据库隔离级别、事务传播机制以及缓存一致性等问题。 接下来,我需要回忆相关的知识点。根据提供的引用,引用[1]提到Spring事务的一致性隔离级别、锁机制有关。引用[2]和[4]讨论了分布式事务和本地事务的处理,但用户的问题可能更多集中在单数据库多线程环境下的查询。引用[3]提到数据库约束,但可能和查询一致性关系不大。 首先,数据库隔离级别是关键。比如,READ_COMMITTED级别可以防止脏读,但可能出现不可重复读。而REPEATABLE_READ则解决不可重复读的问题,但可能导致幻读。用户需要根据场景选择合适的隔离级别,确保事务提交后的数据对其他线程可见。 然后,考虑事务提交后的可见性。在大多数数据库中,事务提交后,修改会持久化到数据库,但其他线程是否能立即看到,取决于数据库隔离级别和可能的复制延迟(如果是分布式数据库)。不过用户的问题可能假设是单数据库,所以重点在隔离级别和缓存。 接下来是应用层的缓存问题。如果应用使用了缓存(如Redis),事务提交后需要及时更新或失效缓存,否则其他线程可能读取到旧数据。这涉及到缓存数据库的双写一致性策略,比如延迟双删或者使用消息队列异步更新缓存。 此外,数据库的锁机制也很重要。比如,在事务中使用SELECT ... FOR UPDATE来加行锁,防止其他事务修改数据,但可能会影响并发性能。用户需要权衡锁的使用和性能。 还有,Spring的事务管理,特别是事务传播行为。比如,PROPAGATION_REQUIRES_NEW可以开启新事务,但可能会影响现有事务。需要确保事务正确提交,避免嵌套事务导致的数据不可见。 另外,数据库的MVCC(多版本并发控制)机制会影响数据的可见性。例如,在MySQL的InnoDB引擎中,事务只能看到已提交的数据版本,但具体可见性取决于事务开始的时间点。因此,长时间的事务可能会导致读取旧数据。 可能的解决方案包括:确保事务正确提交后,其他线程使用新的数据库连接或事务来查询数据;合理设置事务的传播行为和超时时间;使用数据库的显式锁或乐观锁机制;处理应用层的缓存一致性。 用户可能还关心如何在代码中实现这些措施,比如配置Spring的事务管理器,设置隔离级别,或者在查询时强制从数据库读取最新数据(如使用Hibernatesession.refresh())。 需要提醒用户注意不同数据库的行为差异,比如MySQL和PostgreSQL在隔离级别上的实现可能不同。同时,高并发场景下,过高的隔离级别可能导致性能下降,需根据业务需求平衡一致性和性能。 最后,总结关键点:合理配置隔离级别、管理事务边界、处理缓存一致性、使用数据库锁机制,并遵循最佳实践如重试机制和监控。</think>### 多线程环境下事务提交后的数据一致性查询方法 在多线程环境中,事务提交后确保数据一致性的查询需要结合**事务隔离级别**、**数据库锁机制**和**应用层设计**综合解决。以下是具体方法和实践建议: --- #### 1. 合理配置事务隔离级别 - **READ_COMMITTED(读已提交)**:确保事务只能读取已提交的数据,避免脏读。适合大多数场景,但可能因不可重复读导致短暂不一致[^1]。 - **REPEATABLE_READ(可重复读)**:保证同一事务内多次读取结果一致,但可能因幻读问题需要配合锁机制[^1]。 - **设置方法(Spring)**: ```java @Transactional(isolation = Isolation.READ_COMMITTED) public void updateData() { ... } ``` --- #### 2. 确保事务提交后数据可见性 - **显式提交事务**:确保事务完全提交后再发起查询。例如,在Spring中通过`TransactionSynchronizationManager`注册回调: ```java TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { // 事务提交后触发查询 queryLatestData(); } }); ``` --- #### 3. 强制从主库读取最新数据 - **读写分离场景**:若数据库存在主从架构,其他线程可能读到从库的旧数据。需强制查询主库: ```sql /* FORCE_MASTER */ SELECT * FROM table WHERE id = 1; ``` - 或在ORM框架(如MyBatis)中配置主库路由。 --- #### 4. 应用层缓存一致性处理 - **缓存更新策略**: - **延迟双删**:事务提交后删除缓存,短暂延迟后再次删除,确保后续查询重建缓存[^4]。 ```java public void updateData(Data data) { // 1. 删除缓存 cache.delete(data.getId()); // 2. 更新数据库 dataDao.update(data); // 3. 提交后再次异步删除缓存 TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { @Override public void afterCommit() { executor.submit(() -> { sleep(100); // 短暂延迟 cache.delete(data.getId()); }); } }); } ``` - **消息队列触发更新**:通过事务消息(如RocketMQ)异步更新缓存[^4]。 --- #### 5. 数据库锁机制 - **悲观锁**:查询时加锁确保数据独占。 ```sql SELECT * FROM table WHERE id = 1 FOR UPDATE; -- 行锁 ``` - **乐观锁**:通过版本号控制并发更新。 ```sql UPDATE table SET value = 'new', version = version + 1 WHERE id = 1 AND version = old_version; ``` --- #### 6. 监控重试机制 - **监控长事务**:避免未提交事务阻塞其他线程查询。 - **最终一致性补偿**:若短暂不一致可接受,可通过定期比对或消息队列实现最终一致性[^2]。 --- ### 示例场景 假设线程A更新数据,线程B需要立即查询最新值: ```java // 线程A(更新数据) @Transactional public void updateData(int id, String value) { dataDao.update(id, value); // 事务提交后触发缓存失效 TransactionSynchronizationManager.registerSynchronization(...); } // 线程B(查询数据) public String getData(int id) { // 强制从数据库读取(绕过缓存) return dataDao.readFromDB(id); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值