我得到以下休眠错误。我能够识别导致问题的功能。不幸的是,函数中有几个 DB 调用。我无法找到导致问题的行,因为在事务结束时休眠刷新会话。下面提到的休眠错误看起来像一般错误。它甚至没有提到哪个 Bean 导致了这个问题。任何人都熟悉这个休眠错误?
org.hibernate.StaleStateException: Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:93)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:79)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:195)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:584)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransacti
onManager.java:500)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManag
er.java:473)
at org.springframework.transaction.interceptor.TransactionAspectSupport.doCommitTransactionAfterReturning(Transaction
AspectSupport.java:267)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176)
在删除根本不存在的 Id 记录时,我遇到了同样的异常。因此,请检查您正在更新/删除的记录是否确实存在于数据库中
如果没有交易的代码和映射,几乎不可能调查问题。
但是,为了更好地了解导致问题的原因,请尝试以下操作:
在您的休眠配置中,将 hibernate.show_sql 设置为 true。这应该向您显示执行并导致问题的 SQL。
将 Spring 和 Hibernate 的日志级别设置为 DEBUG,这将再次让您更好地了解导致问题的行。
创建一个单元测试来复制问题,而无需在 Spring 中配置事务管理器。这应该让您更好地了解有问题的代码行。
希望有帮助。
解决方案:在 id 属性的 Hibernate 映射文件中,如果您使用任何生成器类,则不应使用 setter 方法显式设置该属性的值。
如果显式设置 Id 属性的值,则会导致上述错误。检查此项以避免此错误。或者当您在映射文件中提到字段生成器 =“本机”或“增量”并且在您的数据库中映射的表不是 auto_incremented 解决方案:转到您的数据库并更新您的表以设置 auto_increment
就我而言,我在两个类似的情况下遇到了这个例外:
在使用@Transactional 注释的方法中,我调用了另一个服务(响应时间很长)。该方法更新了实体的一些属性(在该方法之后,实体仍然存在于数据库中)。如果用户在第二次退出事务方法时请求了两次该方法(因为他认为第一次不起作用),Hibernate 会尝试更新从事务开始时已经改变其状态的实体。当 Hibernate 搜索处于某个状态的实体时,发现相同的实体但已被第一个请求更改,它会抛出异常,因为它无法更新实体。这就像 GIT 中的冲突。
我有更新实体的自动请求(用于监控平台)(以及几秒钟后的手动回滚)。但是这个平台已经被一个测试团队使用了。当测试人员在与自动请求相同的实体中执行测试时(在相同的百分之一毫秒内),我得到了异常。与前一种情况一样,当退出第二个事务时,先前获取的实体已经改变。
结论:就我而言,这不是可以在代码中找到的问题。当 Hibernate 发现第一次从数据库中获取的实体在当前事务期间发生更改时会引发此异常,因此它无法将其刷新到数据库中,因为 Hibernate 不知道哪个是实体的正确版本:当前的那个交易开始时获取;或者已经存储在数据库中的那个。
解决方案:要解决此问题,您必须使用 Hibernate LockMode 来找到最适合您要求的那个。
LockMode.READ
或 LockMode.OPTIMISTIC
。
当我为某些对象(测试)分配特定的 ID,然后我试图将它们保存在数据库中时,这种情况偶然发生在我身上。问题是在数据库中有一个设置对象 ID 的特定策略。如果您有休眠级别的策略,请不要分配 ID。
我刚遇到这个问题,发现我正在删除一条记录,然后在 Hibernate 事务中尝试更新它。
Hibernate 5.4.1 和 HHH-12878 问题
在 Hibernate 5.4.1 之前,乐观锁定失败异常(例如,StaleStateException
或 OptimisticLockException
)不包括失败语句。
创建 HHH-12878 问题是为了改进 Hibernate,以便在引发乐观锁定异常时,也会记录 JDBC PreparedStatement
实现:
if ( expectedRowCount > rowCount ) {
throw new StaleStateException(
"Batch update returned unexpected row count from update ["
+ batchPosition + "]; actual row count: " + rowCount
+ "; expected: " + expectedRowCount + "; statement executed: "
+ statement
);
}
测试时间
我在我的 High-Performance Java Persistence GitHub 存储库中创建了 BatchingOptimisticLockingTest 来演示新行为是如何工作的。
首先,我们将定义一个定义 @Version
属性的 Post
实体,从而启用 the implicit optimistic locking mechanism:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String title;
@Version
private short version;
public Long getId() {
return id;
}
public Post setId(Long id) {
this.id = id;
return this;
}
public String getTitle() {
return title;
}
public Post setTitle(String title) {
this.title = title;
return this;
}
public short getVersion() {
return version;
}
}
我们将使用以下 3 个配置属性启用 JDBC 批处理:
properties.put("hibernate.jdbc.batch_size", "5");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
我们将创建 3 个 Post
实体:
doInJPA(entityManager -> {
for (int i = 1; i <= 3; i++) {
entityManager.persist(
new Post()
.setTitle(String.format("Post no. %d", i))
);
}
});
Hibernate 将执行 JDBC 批量插入:
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
Query: [
INSERT INTO post (title, version, id)
VALUES (?, ?, ?)
],
Params:[
(Post no. 1, 0, 1),
(Post no. 2, 0, 2),
(Post no. 3, 0, 3)
]
所以,我们知道 JDBC 批处理工作得很好。
现在,让我们复制乐观锁定问题:
doInJPA(entityManager -> {
List<Post> posts = entityManager.createQuery("""
select p
from Post p
""", Post.class)
.getResultList();
posts.forEach(
post -> post.setTitle(
post.getTitle() + " - 2nd edition"
)
);
executeSync(
() -> doInJPA(_entityManager -> {
Post post = _entityManager.createQuery("""
select p
from Post p
order by p.id
""", Post.class)
.setMaxResults(1)
.getSingleResult();
post.setTitle(post.getTitle() + " - corrected");
})
);
});
第一个事务选择所有 Post
实体并修改 title
属性。
但是,在刷新第一个 EntityManager
之前,我们将使用 executeSync
方法执行第二个转换。
第二个事务修改了第一个 Post
,因此它的 version
将递增:
Query:[
UPDATE
post
SET
title = ?,
version = ?
WHERE
id = ? AND
version = ?
],
Params:[
('Post no. 1 - corrected', 1, 1, 0)
]
现在,当第一个事务尝试刷新 EntityManager
时,我们将获得 OptimisticLockException
:
Query:[
UPDATE
post
SET
title = ?,
version = ?
WHERE
id = ? AND
version = ?
],
Params:[
('Post no. 1 - 2nd edition', 1, 1, 0),
('Post no. 2 - 2nd edition', 1, 2, 0),
('Post no. 3 - 2nd edition', 1, 3, 0)
]
o.h.e.j.b.i.AbstractBatchImpl - HHH000010: On release of batch it still contained JDBC statements
o.h.e.j.b.i.BatchingBatch - HHH000315: Exception executing batch [
org.hibernate.StaleStateException:
Batch update returned unexpected row count from update [0];
actual row count: 0;
expected: 1;
statement executed:
PgPreparedStatement [
update post set title='Post no. 3 - 2nd edition', version=1 where id=3 and version=0
]
],
SQL: update post set title=?, version=? where id=? and version=?
因此,您需要升级到 Hibernate 5.4.1 或更高版本才能从这项改进中受益。
当触发器执行影响行数的附加 DML(数据修改)查询时,可能会发生这种情况。我的解决方案是在触发器顶部添加以下内容:
SET NOCOUNT ON;
我面临同样的问题。该代码在测试环境中工作。但它在暂存环境中不起作用。
org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 3; expected: 1
问题是该表在测试数据库表中的每个主键都有一个条目。但是在暂存数据库中,相同的主键有多个条目。 (问题是在暂存数据库中,该表没有任何主键约束,也有多个条目。)
所以每次更新操作都会失败。它尝试更新单个记录并期望更新计数为 1。但由于表中有 3 条记录具有相同的主键,结果更新计数找到 3。由于预期更新计数和实际结果更新计数不匹配, 它抛出异常并回滚。
之后我删除了所有具有重复主键的记录并添加了主键约束。它工作正常。
Hibernate - Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
实际行数:0 // 表示没有找到要更新的记录 update: 0 // 表示没有找到记录,所以没有任何更新预期: 1 // 表示预期至少有 1 条记录与 db 表中的键。
这里的问题是查询试图更新某个键的记录,但是休眠没有找到任何具有该键的记录。
当您尝试UPDATE
主键 时也会发生这种情况。
正如 Julius 所说,当更新发生在其子级被删除的对象上时,就会发生这种情况。 (可能是因为需要更新整个父对象,有时我们更喜欢删除子对象并将它们重新插入父对象(新的,旧的无关紧要)以及父亲可能对任何其他对象进行的任何其他更新它的其他普通字段)所以...为了使它起作用,通过调用 childrenList.clear()
删除子项(在事务中)(不要循环遍历子项并使用一些 childDAO.delete(childrenList.get(i).delete()))
删除每个子项并在父亲对象的一侧。然后更新父亲(fatherDAO.update(father))。(对每个父亲对象重复)结果是孩子们与父亲的链接被剥离,然后他们被框架作为孤儿移除.
我遇到了这个问题,我们有一对多的关系。
在 master 的 hibernate hbm 映射文件中,对于具有集合类型排列的对象,添加了 cascade="save-update"
并且它工作正常。
如果没有这个,默认情况下,hibernate 会尝试更新不存在的记录,并通过这样做来代替插入。
出现此错误的另一种方法是,如果您的集合中有一个空项目。
我遇到了同样的问题,我验证这可能是由于自动递增主键而发生的。为了解决这个问题,不要用数据集插入自动增量值。插入没有主键的数据。
当您尝试 delete
一个对象然后您尝试 update
同一个对象时,就会发生这种情况。在 delete
之后使用它:
session.clear();
这也发生在我身上,因为我的 id 为 Long,并且我从视图中接收到值 0,当我尝试保存在数据库中时出现此错误,然后我通过将 id 设置为 null 来修复它。
这个问题主要发生在我们试图保存或更新已经被正在运行的会话提取到内存中的对象时。如果您已从会话中获取对象并尝试在数据库中更新,则可能会引发此异常。
我使用了 session.evict();要首先删除存储在休眠中的缓存,或者如果您不想冒丢失数据的风险,最好创建另一个对象来存储数据临时。
try
{
if(!session.isOpen())
{
session=EmployeyDao.getSessionFactory().openSession();
}
tx=session.beginTransaction();
session.evict(e);
session.saveOrUpdate(e);
tx.commit();;
EmployeyDao.shutDown(session);
}
catch(HibernateException exc)
{
exc.printStackTrace();
tx.rollback();
}
当我在注释为 @Transactional
的方法内手动开始和提交事务时遇到了这个问题。我通过检测是否已经存在活动事务来解决问题。
//Detect underlying transaction
if (session.getTransaction() != null && session.getTransaction().isActive()) {
myTransaction = session.getTransaction();
preExistingTransaction = true;
} else {
myTransaction = session.beginTransaction();
}
然后我允许 Spring 处理提交事务。
private void finishTransaction() {
if (!preExistingTransaction) {
try {
tx.commit();
} catch (HibernateException he) {
if (tx != null) {
tx.rollback();
}
log.error(he);
} finally {
if (newSessionOpened) {
SessionFactoryUtils.closeSession(session);
newSessionOpened = false;
maxResults = 0;
}
}
}
}
当您将 JSF 托管 Bean 声明为
@RequestScoped;
当你应该声明为
@SessionScoped;
问候;
当我尝试使用数据库中不存在的 id 更新对象时出现此错误。我犯错的原因是我手动为对象的客户端 JSON 表示分配了一个名为“id”的属性,然后在服务器端反序列化对象时,这个“id”属性将覆盖实例变量( Hibernate 应该生成的也称为 'id')。因此,如果您使用 Hibernate 生成标识符,请注意命名冲突。
我也遇到了同样的挑战。在我的例子中,我正在使用 hibernateTemplate
更新一个甚至不存在的对象。
实际上,在我的应用程序中,我正在获取要更新的数据库对象。在更新它的值时,我也错误地更新了它的 ID,并继续更新它并遇到了上述错误。
我将 hibernateTemplate
用于 CRUD 操作。
在阅读了所有答案后,没有找到任何人谈论休眠的逆属性。
在我看来,您还应该在您的关系映射中验证是否适当地设置了反向关键字。创建反向关键字来定义维护关系的所有者是哪一方。更新和插入的过程因该属性而异。
假设我们有两个表:
主体表、中间表
具有一对多的关系。休眠映射类分别是 Principal 和 Middle。
所以 Principal 类有一组 Middle 对象。 xml 映射文件应如下所示:
<hibernate-mapping>
<class name="path.to.class.Principal" table="principal_table" ...>
...
<set name="middleObjects" table="middle_table" inverse="true" fetch="select">
<key>
<column name="PRINCIPAL_ID" not-null="true" />
</key>
<one-to-many class="path.to.class.Middel" />
</set>
...
由于 inverse 设置为“true”,这意味着“Middle”类是关系所有者,因此 Principal 类不会更新关系。
所以更新过程可以这样实现:
session.beginTransaction();
Principal principal = new Principal();
principal.setSomething("1");
principal.setSomethingElse("2");
Middle middleObject = new Middle();
middleObject.setSomething("1");
middleObject.setPrincipal(principal);
principal.getMiddleObjects().add(middleObject);
session.saveOrUpdate(principal);
session.saveOrUpdate(middleObject); // NOTICE: you will need to save it manually
session.getTransaction().commit();
这对我有用,但是您可以建议一些版本以改进解决方案。这样我们都会学习。
在我们的案例中,我们终于找到了 StaleStateException 的根本原因。
事实上,我们在一个休眠会话中删除了该行两次。之前我们使用的是 ojdbc6 lib,这在这个版本中是可以的。
但是当我们升级到odjc7或ojdbc8时,两次删除记录是抛出异常。我们的代码中存在错误,我们删除了两次,但这在 ojdbc6 中并不明显。
我们能够用这段代码重现:
Detail detail = getDetail(Long.valueOf(1396451));
session.delete(detail);
session.flush();
session.delete(detail);
session.flush();
在第一次刷新休眠时,会在数据库中进行更改。在第二次刷新期间,休眠将会话的对象与实际表的记录进行比较,但找不到,因此出现异常。
我解决了。我发现表中的 Id 列没有主键。一旦我创建它就为我解决了。在表中还发现了重复的 id,在此之前我删除并解决了它。
如果您使用本机 sql 查询更改数据集中的某些内容,但会话缓存中存在相同数据集的持久对象,则会发生这种情况。使用 session.evict(yourObject);
Hibernate 缓存会话中的对象。如果对象被超过 1 个用户访问和修改,则可能会抛出 org.hibernate.StaleStateException
。在保存或使用锁之前,它可以通过合并/刷新实体方法来解决。更多信息:http://java-fp.blogspot.lt/2011/09/orghibernatestalestateexception-batch.html
案例之一
SessionFactory sf=new Configuration().configure().buildSessionFactory();
Session session=sf.openSession();
UserDetails user=new UserDetails();
session.beginTransaction();
user.setUserName("update user agian");
user.setUserId(12);
session.saveOrUpdate(user);
session.getTransaction().commit();
System.out.println("user::"+user.getUserName());
sf.close();
我正面临这个异常,并且休眠运行良好。我尝试使用 pgAdmin 手动插入一条记录,这里问题变得清晰了。 SQL 插入查询返回 0 插入。并且有一个触发函数会导致此问题,因为它返回 null。所以我只需将其设置为返回新的。最后我解决了这个问题。
希望对任何身体都有帮助。
我收到此错误是因为我错误地使用 Id(x => x.Id, "id").GeneratedBy.**Assigned**();
映射了 ID
列
使用 Id(x => x.Id, "id").GeneratedBy.**Identity**();
解决的问题
这发生在我身上,因为我在 bean 类中缺少 ID 声明。
不定期副业成功案例分享