Hibernate-批更新从更新中返回意外的行计数:0实际行计数:0应为:1

我得到以下休眠错误。我能够确定导致问题的功能。不幸的是,函数中有几个数据库调用。我无法找到导致问题的行,因为Hibernate在事务结束时刷新会话。下面提到的休眠错误看起来像一个一般错误。它甚至没有提到是哪个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)
394021 次浏览

如果没有事务的代码和映射,就几乎不可能调查问题。

但是,要更好地处理导致问题的原因,请尝试以下操作:

  • 在Hibernate配置中,将hibernate.show_SQL设置为true.这将向您显示执行并导致问题的SQL.
  • 将Spring和Hibernate的日志级别设置为DEBUG,同样,这将使您更好地了解是哪一行导致了问题。
  • 创建一个复制问题的单元测试,而无需在Spring中配置事务管理器。这将使您更好地了解有问题的代码行。

希望对你有帮助。

我在按ID删除根本不存在的记录时遇到了相同的异常。因此,请检查您正在更新/删除的记录是否确实存在于数据库中

我刚刚遇到了这个问题,发现我正在删除一条记录,然后试图在Hibernate事务中更新它。

正如朱利叶斯所说,当更新发生在其子对象被删除的对象上时,就会发生这种情况。(可能是因为需要更新整个父对象,有时我们更愿意删除子对象并将它们重新插入到父对象中(新的、旧的并不重要),以及父对象在其任何其他普通字段上可能具有的任何其他更新) 所以..为了使其工作,通过调用childrenList.clear()来删除子代(在事务中)(不要遍历子代,并使用某个childDAO.delete(childrenList.get(i).delete()))和设置来删除每个子代 __父亲对象一侧的ABC0。然后更新父亲(fatherdao.update(father))。(对每个父亲对象重复)结果是,孩子与父亲的链接被剥离,然后他们作为孤儿被框架删除。

当你试图delete一个对象,然后你试图update同一个对象时,就会发生这种情况。在delete之后使用:

session.clear();

当触发器执行影响行数的其他DML(数据修改)查询时,可能会发生这种情况。我的解决方案是在触发器的顶部添加以下内容:

SET NOCOUNT ON;

我曾经偶然遇到过这种情况,当时我正在为一些对象分配特定的ID(测试),然后我试图将它们保存在数据库中。问题是在数据库中有一个用于设置对象ID的特定策略。如果您有一个休眠级别的策略,只需不要分配一个ID.

我遇到了同样的问题,我验证了这可能是因为自动增加主键。要解决此问题,请不要在数据集中插入自动增量值。插入没有主键的数据。

我遇到了这个问题,我们有一对多的关系。

在主对象的Hibernate HBM映射文件中,对于具有设置类型排列的对象,添加了cascade="save-update",它工作得很好。

否则,默认情况下,Hibernate会尝试更新不存在的记录,并通过这样做来插入。

解决方案: 在ID属性的Hibernate映射文件中,如果使用任何生成器类,则不应使用setter方法显式设置该属性的值。

如果显式设置ID属性的值,则会导致上面的错误。选中此项可避免此错误。 或 当您在映射文件中提到字段生成器=“ Native ”或“ Incremental ”,并且在您的数据库中映射的表不是自动_递增时,就会显示错误。 解决方案:转到数据库并更新表以设置自动_增量

当我在标注为@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;
}
}
}
}

这种情况也发生在我身上,因为我的ID很长,并且我从视图中接收到值0,当我尝试在数据库中保存时,我得到了这个错误,然后我通过将ID设置为NULL来修复它。

当您将JSF托管bean声明为

@RequestScoped;

当你应该声明为

@SessionScoped;

问候;

我也面临着同样的问题。 代码在测试环境中正常运行。但它在暂存环境中不起作用。

org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 3; expected: 1

问题是在测试DB表中,表的每个主键只有一个条目。但在临时数据库中,同一主键有多个条目。(问题是在暂存数据库中,表没有任何主键约束,而且有多个条目。)

所以每次更新操作都会失败。它尝试更新单个记录并期望获得更新计数为1。但由于表中有3条记录具有相同的主键,因此结果更新计数为3。由于预期更新计数和实际结果更新计数不匹配,因此它引发异常并回滚。

之后,我删除了所有具有重复主键的记录,并添加了主键约束。它工作正常。

Hibernate - Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1

实际行数:0//表示未找到要更新
的记录 更新
:0//表示未找到记录,因此不更新 预期:1//表示预期至少有1条在DB表中具有键的记录。

这里的问题是,查询试图更新某个键的记录,但Hibernate没有找到任何具有该键的记录。

当我尝试使用数据库中不存在的ID更新对象时,出现了此错误。我犯错误的原因是,我手动将一个名为“ ID ”的属性分配给对象的客户端JSON表示,然后当在服务器端反序列化对象时,这个“ ID ”属性将覆盖Hibernate应该生成的实例变量(也称为“ ID ”)。因此,如果您使用Hibernate生成标识符,请注意命名冲突。

如果您使用原生SQL查询更改了数据集中的某些内容,但同一数据集的持久化对象存在于会话缓存中,则会发生

这种情况。 使用session.evict(yourobject);

我也遇到了同样的挑战。在我的例子中,我使用hibernateTemplate更新了一个根本不存在的对象。

实际上,在我的应用程序中,我要更新一个数据库对象。在更新它的值时,我还错误地更新了它的ID,并继续更新它,遇到了上述错误。

我使用hibernateTemplate进行CRUD操作。

Hibernate缓存会话中的对象。如果对象被多个用户访问和修改,则可能抛出org.hibernate.StaleStateException。在保存或使用锁定之前,可以使用合并/刷新实体方法解决此问题。更多信息:http://java-fp.blogspot.lt/2011/09/orghibernatestalestateexception-batch.html

在阅读了所有的答案后,没有发现任何人谈论Hibernate的逆属性。

在我看来,你还应该在你的关系映射中验证是否正确设置了相反的关键字。创建相反的关键字来定义哪一方是维护关系的所有者。更新和插入的过程根据此属性的不同而不同。

假设我们有两个表:

主要_表格中间_表

具有一对多的关系。Hiberntate映射类分别是校长中间

因此,校长类具有一组中间对象。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 ”,这意味着“中间”类是关系所有者,因此Principal类将__关系ABC0。

因此,更新的过程可以这样实现:

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();

这对我来说很有效,但你可以建议一些版本来改进解决方案。这样我们都能学到东西。

案件之一

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();

我得到这个错误是因为我错误地使用Id(x => x.Id, "id").GeneratedBy.**Assigned**();映射了ID列。

使用Id(x => x.Id, "id").GeneratedBy.**Identity**();解决的问题

我遇到了这个异常,而Hibernate运行得很好。我尝试使用PgAdmin手动插入一条记录,在这里问题变得很清楚。SQL插入查询返回0插入。有一个触发器函数会导致此问题,因为它返回NULL.因此,我只需将其设置为返回新的。 最后我解决了这个问题。

希望对任何人都有帮助。

当您尝试将ABC0__主键时,也可能发生这种情况。

在我的案例中,我在两个类似的案例中发现了这个例外:

  • 在使用@Transactional注释的方法中,我调用了另一个服务(响应时间很长)。该方法更新了实体的一些属性(在该方法之后,实体仍然存在于数据库中)。如果用户在第二次退出事务方法时两次请求该方法(因为他认为第一次不起作用),则Hibernate会尝试更新从事务开始就已更改其状态的实体。当Hibernate搜索一个处于状态的实体时,发现了相同的实体,但第一个请求已经更改了,它会抛出一个异常,因为它无法更新该实体。这就像是饭桶里的冲突。
  • 我有更新实体的自动请求(用于监控平台)(以及几秒钟后的手动回滚)。但是这个平台已经被一个测试团队使用了。当测试人员在与自动请求相同的实体中执行测试时(在相同的百分之一毫秒内),我得到了异常。如在先前的情况中,当从第二事务退出时,先前获取的实体已经改变。

结论:在我的例子中,这不是一个可以在代码中找到的问题。当Hibernate发现首次从数据库获取的实体在当前事务期间发生更改时,将引发此异常。,所以它不能将其刷新到数据库中,因为Hibernate不知道哪个是实体的正确版本:当前事务在开始时获取的版本;或者已经存储在数据库中的那个。

解决方案:要解决这个问题,您必须使用Hibernate锁定模式来找到最适合您需求的那个。

这种情况发生在我身上,因为我在bean类中缺少ID声明。

在我的例子中,数据库有一个问题,因为其中一个存储的进程消耗了所有的CPU,导致高数据库响应时间。一旦这个被杀了,问题就解决了。

实际上,当我没有将对象存储为引用变量时,就会发生这种情况。在实体类中。像这样的代码: ses.get(InsurancePolicy.class, 101);之后,我将对象存储在实体的引用变量中,这样问题就解决了。policy=(InsurancePolicy)ses.get(InsurancePolicy.class, 101); 在那之后,我更新了对象,它工作得很好。

我收到了同样的信息。 在查找与代码相关的源代码后,我发现在本地计算机上运行应用程序会干扰开发阶段,因为它们共享相同的数据库。 因此,有时一个服务器已经删除了一个条目,而另一个服务器只是想做同样的事情。

获得此错误的另一种方法是在集合中有一个空项。

我调试这个错误的几种方法:

  1. 如“接受的答案”中所建议的-打开“显示SQL ”。
  2. 我发现在Hibernate SQL中设置ID时存在一些问题。
  3. 发现缺少@GeneratedValue(strategy=GenerationType.Identity)

在我们的例子中,我们最终找到了StaleStateException的根本原因。

事实上,我们在单个Hibernate会话中删除了该行两次。早些时候,我们使用OJDBC6库,这在这个版本中是可以的。

但是当我们升级到ODJC7或OJDBC8时,删除两次记录就会抛出异常。我们的代码中有一个错误,我们删除了两次,但这在OJDBC6中并不明显。

我们能够用这段代码重现:

Detail detail = getDetail(Long.valueOf(1396451));
session.delete(detail);
session.flush();
session.delete(detail);
session.flush();

在第一次刷新时,Hibernate会在数据库中进行更改。在第二次刷新期间,Hibernate将Session的对象与实际表的记录进行比较,但找不到,因此出现异常。

这个问题主要发生在我们试图保存或更新已经被运行的会话获取到内存中的对象时。 如果您已经从会话中获取了一个对象,并且正在尝试在数据库中进行更新,则可能会引发此异常。

我使用了session.evict();首先删除存储在Hibernate中的缓存,或者如果您不想冒丢失数据的风险,最好创建另一个对象来存储数据Temp.

     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();
}

Hibernate 5.4.1和HHH-12878问题

在Hibernate 5.4.1之前,乐观锁定失败异常(例如,StaleStateExceptionOptimisticLockException)不包括失败语句。

创建HHH-12878问题是为了改进Hibernate,以便在抛出乐观锁定异常时,也记录JDBCPreparedStatement实现:

if ( expectedRowCount > rowCount ) {
throw new StaleStateException(
"Batch update returned unexpected row count from update ["
+ batchPosition + "]; actual row count: " + rowCount
+ "; expected: " + expectedRowCount + "; statement executed: "
+ statement
);
}

测试时间

我在我的高性能Java持久性GitHub存储库中创建了BatchingOptimisticLockingTest来演示新行为是如何工作的。

首先,我们将定义Post实体,该实体定义@Version属性,从而启用隐式乐观锁定机制

@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=?

因此,您需要升级到Hibernate5.4.1或更高版本才能从这一改进中受益。

在我的例子中,这是因为在插入原因之前触发了触发器(实际上它意味着使用时间戳将一个大表拆分为多个表),然后返回无效。所以我在使用SpringBoot JPA save()函数时遇到了这个问题。

除了上面提到的将触发器改为SET NOCOUNT ON;塔先生外,解决方案还可以是使用本机查询

insert into table values(nextval('table_id_seq'), value1)

此错误的原因通常是您发送了一个或多个错误的主键,请在使用新值加载变量以执行更新之前验证变量是否干净。

移除cascade=CascadeType.ALL为我解决了这个问题。

我解决了。我发现表中我的ID列没有主键。 一旦我创造了它,它就为我解决了。此外,在表中发现了重复的ID,在此之前,我删除并解决了它。

在我的例子中,我通过MySQL Workbench错误地创建了**two**类似的primary keys

如果您遇到这种情况,请尝试从MySQL工作台中删除一个。

enter image description here

我在将Hibernate与MySQL一起使用时遇到了这个问题,在将ID数据类型从UUID更改为String后,问题得到了解决。但我不知道原因。

我的两分钱。

Spring Boot 2.7.1的问题: H2数据库版本已更改为v2.1.214,这可能会导致在将生成的UUID用于ID列时引发OptimisticLockException,请参阅https://hibernate.atlassian.net/browse/hhh-15373

解决方案:columnDefinition="UUID"添加到@Column注释中

例如,实体的主键定义如下:

@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = COLUMN_UUID, updatable = false, nullable = false)
UUID uUID;

将列注释更改为:

@Column(name = COLUMN_UUID, updatable = false, nullable = false, columnDefinition="UUID")