在通过 JPA 启动 Hibernate 或者 单机模式启动 Hibernate 时,你会发现 Hibernate 的 AUTO flush 模式会表现出不同的行为。
当使用 JPA 时 ,AUTO flush 模式会在所有查询方法触发时(JPQ,Criteria API,和原生 SQL)之前触发一个
flush
操作优先于查询执行操作。然而在使用原生 API 启动 Hibernate 的时候就不会这样。
Session flush 在 COMMIT 之前默认都会执行,也可以手动执行,它主要做了两件事情:
-
清理缓存
-
执行 SQL
flush : hibernate 对于当前会话,按照当前内存中对象属性的变化来同步更新数据库
在开启了事务的情况下
-
首先我们使用
hibernate
执行了一次查询
-
然后我们对获取到的对象进行修改
-
再次执行查询。
hibernate 会在第二次查询之前判断该对象是否已经被修改过,如果已经被修改过,执行更新操作来更新数据库中的记录使其与内存中的数据一致。
默认情况下 ,hibernate 会在以下情况下调用 flush:
-
直接调用 session.flush
-
当应用调用 Transaction.commit() 时,会先调用 flush ,然后再想数据库提交
-
在做查询时 (HQL,Criteria),如果缓存中持久化对象的属性发生变化,会先 flush 缓存,以保证查询结果是最新的数据
如果对象使用 native 生成器生成 ID时,在当调用 session.save() 去保存对象是,会直接向数据库插入该实体的 insert 语句。
FlushMode
|
说明
|
NEVER
|
已经废弃了,被
MANUAL
取代了
|
MANUAL
|
禁止更新的模式,更新则会抛出异常
|
AUTO
|
设置成
AUTO
之后,当程序进行查询,提交事务或者调用 session.flush() 的时候,都会刷新数据库
|
COMMIT
|
提交事务或者 session.flush() 时,刷新数据库,查询不刷新
|
ALWAYS
|
每次进行查询、提交事务、session.flush() 的时候都会刷新数据库
|
ALWAYS
和
AUTO
的区别:当 Hibernate 缓存中的对象被改动之后,会标记为脏数据(即与数据库不一致了)。当 session 设置为
FlushMode.AUTO
时,Hibernate 在进行查询的时候会判断缓存中的数据是否在脏数据,是则刷新数据库,不是则不刷。而
ALWAYS
是直接刷新,不进行任何判断。很显然
AUTO
要比
ALWAYS
要高效的多。
如果 session 中某个对象的属性发生了改变,使用 flush() 的方式,去更改数据库中的记录,使其和改变后的 session 中的对象一致
session.flush : 执行了一批 SQL ,但是不提交
session.commit: 会先执行 flush ,然后提交事务,提交事务意为这对数据库的操作永久保存,不可 rollback。
大多数人会认为 Hibernate 会在执行所有查询前执行
flush session
操作。而该操作是可以直接设置的,我们可以通过修改
FlushModeType
选项来实现配置。对于 Hibernate 来说,如果本次查询前没有必要执行
插入/更新/删除
操作,那么可以通过该设置来避免不必要的 flush 操作。
JPA’s AUTO FlushModeType
Reference document
如参考手册中所陈述的,
AUTO flush
策略会在执行查询前有可能同步当前持久化上下到数据库中。对于 Hibernate 框架的作者而言如果将配置选项起名为
FlushMode.SOMETIMES
可能更加通俗易懂些。
如大多数ORM(对象映射模型)解决方案一样,Hibernate 基于SQL92 语法标准提供了一套对象查询语言(JPQL/HQL)。
这套对象查询语言可以基于当前数据库种类(MySQL、Oracle)翻译成对应格式的 SQL。因此 Hibernate 必须对不同种类的数据库提供相同的功能,实现了数据库访问的平台无关性。因为大多数数据库系统都是基于
SQL-92
标准实现的,因此 Hibernate的对象查询语言实现了对大多数数据库查询语法的抽象。
你可以在大多数用例(查询对象或者对象映射)下使用这套对象查询语言。对于 Hibernate 不支持的高级查询请求是有限的。无论什么时候当我们想要使用一些具体的查询技术时,除了运行原生的 SQL我么可能没有其他选择。
Hibernate 是一个持久化框架。但是 Hibernate 并没有试图永久取代 SQL 。如果一些查询使用原生 SQL 表达更容易,那么在以牺牲应用性能来提高应用的数据库移植的便利性这种做法是不值当的。
首先我们将要测试通过 HQL 即将进行查询时
AUTO flush
模式是如何运作的。我们先来定义两个不相关的实体:
-
新建一个 Product 实体
-
获取一个 User 实体应该不会触发持久化上下文 flush
-
查询 Product 实体,此时 AUTO flush 应该会触发状态同步(一个基于 Product实体的 INSERT 语句应该会先于 SELECT 语句被执行)
Product product = new Product();
product.setColor("Blue");
session.persist(product);
assertEquals(0L,session.createQuery("select count(id) from User").getSingleResult());
assertEquals(product.getId(),session.Query("select p.id from Product p").getSingleResult());
上述代码执行完成后会出现以下 SQL 输出:
SELECT count(user0_.id) AS col_0_0_
FROM USER user0_
INSERT INTO product (color, id)
VALUES ('Blue', 'f76f61e2-f3e3-4ea4-8f44-82e9804ceed0')
SELECT product0_.id AS col_0_0_
FROM product product0_
如上所述,select * from user
SQL 并没有触发 session flush
。这是因为 Hibernate 会检查当前查询空间与挂起的表的状态。如果当前执行的查询语句与未刷新的表的语句关联的表不重叠,那么flush
操作就会被忽略。
session.persist(product);
assertEquals(
session.createQuery(
"select count(*) " +
"from User u " +
"where u.favoriteColor in (" +
" select distinct(p.color) from Product p" +
).getSingleResult()
执行结果:
INSERT INTO product (color, id)
VALUES ('Blue', '2d9d1b4f-eaee-45f1-a480-120eb66da9e8')
SELECT count(*) AS col_0_0_
FROM USER user0_
WHERE user0_.favoriteColor IN (
SELECT DISTINCT product1_.color
FROM product product1_
session.persist(product);
assertEquals(
session.createQuery(
"select count(*) " +
"from User u, Product p " +
"where u.favoriteColor = p.color"
).getSingleResult()
执行结果:
INSERT INTO product (color, id)
VALUES ('Blue', '4af0b843-da3f-4b38-aa42-1e590db186a9')
SELECT count(*) AS col_0_0_
FROM USER user0_
CROSS JOIN product product1_
WHERE user0_.favoriteColor=product1_.color
以上类型的代码都能够触发 Product 的 session flush
的原因是 Hibernate 可以解析 HQL 并将其翻译为 SQL 查询。Hibernate 无法映射一张不存在的表,因此通过 HQL/JPQL
查永远都会命中数据库中的表。
所以,Hibernate 只能够感知到哪些我们显式将实体映射到数据库中表的对象。Hibernate 无法感知到当前挂起的DML
操作是否隐含数据库触发器或者数据库级别的级联,。因此哪怕是 HQL ,AUTO flush 模式也有可能引发一致性问题。
当涉及到原生的 SQL 查询时,AUTO flush的动作将会变得更加复杂,因为 Hibernate 只能够解析 (HQL/JPQL)语法,而无法解析原生的 SQL 查询。而各种数据库系统的实现也提供了各种其本身所拥有的特性,而 Hibernate 无法全部支持到。
Product product = new Product();
product.setColor("Blue");
session.persist(product);
assertEquals(
session.createNativeQuery("SELECT COUNT(*) FROM product").getSingleResult()
执行结果:
SELECT COUNT(*)
FROM product
INSERT INTO product (color, id)
VALUES ('Blue', '718b84d8-9270-48f3-86ff-0b8da7f9af7c')
可以看到,新创建的 Product 对象只有在事务提交的时候才会被插入因为原生的SQL 查询并不会触发 session flush
操作。这样一个引发一致性的问题很难被许多开发者断点调试到。
同样的问题也存在于基于注解的原生查询中:
@NamedNativeQuery(name = "product_ids", query = "SELECT COUNT(*) FROM product")
我们同样也无法看到新添加的记录
assertEquals(0, session.getNamedQuery("product_ids").getSingleResult());
因此哪怕 SQL 查询被预加载了,Hibernate 同样不会提取关联的查询表空间来对应正在挂起的 DML
语句。
值的注意的是该类场景适用于 Hibernate 创建的接口,而不是 JPA AUTO flush 模式
即使当前 session 定义了一个默认的 flush 策略,开发者仍然可以在查询操作中重写它。
ALWAYS
模式会在所有查询执行前都会执行 flush 操作,此时 Hibernate 在当前事务下不会基于当前缓存的对象状态进行 flush 同步操作的调优。
assertEquals(
product.getId(),
session.createNativeQuery("select id from product")
.setFlushMode(FlushMode.ALWAYS)
.getSingleResult()
你同样可以添加一个同步规则给你当前执行的 SQL 查询语句。Hibernate 将会通过该策略来判断那张表需要在执行查询操作前需要被同步。这个操作在二级缓存中是非常有用的。
assertEquals(
product.getId(),
session.createNativeQuery(
"select id from product")
.addSynchronizedEntityClass(Product.class)
.getSingleResult());
AUTO flush
模式是十分复杂的,而且在该模式下需要开发者在查询语句中处理一致性问题也是一场噩梦。如果你决定在数据库中添加一个触发器,那么你将不得不检查所有的 Hibernate 语句来确保他们最终会与老数据冲突。
我的建议是设置 FLUSH MODE
为 ALWAYS
,因为该模式下 flush 的行为 更加接近于 JPA 所定义的 AUTO FlushModeType
。
数据不一致对于一些偶然的不完整的 flush 场景不仅仅是一个小问题。当 DML 操作语句与 查询语句混合操作是有可能引发不必要的 flush,而这种场景并不难减少。最好将查询操作置于事务的开始(当缓存的数据库实体对象完全同步场景),在事务的结束(当前的持久化上下文将要被 flush)
对象状态的事务操作应该被置于事务结束之前,尝试避免将其置于查询之间(从而阻止不完整的 flush操作 触发)
Hibernate session.flush 使用
Flushing the Session
How does AUTO flush strategy work in JPA and Hibernate
JPA 和 Hiberante 的 AUTO flush 机制是如何工作的?介绍在通过 JPA 启动 Hibernate 或者 单机模式启动 Hibernate 时,你会发现 Hibernate 的 AUTO flush 模式会表现出不同的行为。当使用 JPA 时 ,AUTO flush 模式会在所有查询方法触发时(JPQ,Criteria API,和原生 SQL)之前触发一个flush 操作优先于查询执行操作。然而在使用原生 API 启动 Hibernate 的时候就不会这样。什么是 Hibern
JPA提供一个持久化上下文作为一级缓存,提供自动脏检查.对应某个id的实例在持久化上下文中只有一个对象.
查询时总是尝试在当前上下文中先搜索对象,不存在再触发数据库查询.
托管状态的bean会建立一个和缓存数据的联系,这时bean的属性改变同时会修改缓存数据,此时这条数据就变成了脏数据。
实体状态详解
临时状态:
实际上就是new了一个普通的JavaBea...
2、当有id值时,修改数据库中的数据
public void persist(Object entity)
persist 方法可以将实例转换为 managed( 托管 ) 状态。在调用 flush() 方法或提交事物后,实例将会被插入到数据库中。
对不同状态下的实例 A , persist 会产生以下操作 :
如果 A 是一个 new 状态的实体,它将会转为 managed 状态;
如果 A 是一个 managed 状
zz from :
http://www.codeinstructions.com/2009/04/read-only-transactions-with-spring-and.html
Read-Only transactions with Spring and Hibernate
Spring supports the concept of read-only trans...
在Hibernate中持久化上下文的flush操作模式中,JPA还支持COMMIT(JPA只支持AUTO和COMMIT两种)。对于COMMIT的flush操作模式,JPA针对HQL查询和native SQL查询有不同的执行:
对于HQL查询,无论是否涉及到了被缓冲的Entity对象,都只会在当前事务提交的时候执行flush操作对于native SQL查询,如果涉及到了被缓冲的Entity
遇到持久化上下文的事务提交,那么在事务提交之前执行flush操作在即将执行的HQL查询中涉及到了被缓冲的Entity对象在即将执行的native SQL查询中涉及到了被缓冲的Entity对象
下面以JPA的EntityManager为例介绍flushMode
通过EntityManager的flush()可手动地控制将实体类中的数据传送到数据库,
但这句话意思十分含糊。具体地说应该是:
客户端对实体类中数据的改变和手写的任何SQL语句都是保存在客户端的内存中,
当执行了flush()后,对...
更新数据时,为何要先查再改
Jpa实体类更新会将实体类为空的字段也更新称null,也就说业务上可能只需要更新用户的手机号,但是用户这个实体类还有年龄、地址等其他属性…你在更新用户的时候,需要把这些你没有改的属性也赋值到user对象上,,这时候先查一遍user能获得完整的属性
如果你的user实体类有@Version注解修饰的属性时,更需要.
1 .flush ():同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
2.refresh (Object entity):用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。
3.clear ():清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。
4.contains (Object entity):判断一个实例是否属于...
T_Run_Config trc = em.find(T_Run_Config.class, "SCAN_LOG");
int newValue = Integer.valueOf(trc.getTheValue()) + dataSize;
最近在修复组里项目的一个bug时,发现这个bug是对Spring Data JPA的使用不当所导致。在修复成功这个bug后,由于对Spring Data JPA的了解甚少,所以我打算把解决bug过程中查阅的相关资料写成博客做个总结,博客的内容主要针对初学者,内容简单。
首先模拟一下bug产生的过程,下列代码的逻辑可能会与我们正常编写的代码逻辑有点不一致,但重要的是通过代码去理解bug产生的原因:
@Service
public class UserService {
@Autowired
初学者中的小白:
nodejs 使用 fetch 以及遇到的一些问题
ghimi: