ChatGPT解决这个技术问题 Extra ChatGPT

休眠 - 批量更新从更新返回意外的行数:0 实际行数:0 预期:1

我得到以下休眠错误。我能够识别导致问题的功能。不幸的是,函数中有几个 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)
谢谢@Peter Mortensen。我已经更新了我的电子邮件。
我也有同样的问题。这不是一个大问题,因为它很少发生。使用 show_sql 是不切实际的,因为重现此行为需要数百万个事务。但是,由于在我运行的系统测试期间重复发生这种情况(有数以亿计的事务),我怀疑有一个特定的原因。
我在尝试更新没有更改的行时遇到了这个问题。如果没有差异,请不要更新。

s
shreyas

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


当我从父子关系中删除一个孩子,保存父母(删除孩子)然后尝试手动删除孩子时,我遇到了这个问题。
这解决了我的问题。该记录不存在,我的服务正在调用 updateAll() 方法,而它实际上需要调用 createOrUpdateAll() 方法。谢谢。
那么如何解决这个问题呢?如果我得到一条记录,然后删除它;但是如果系统在我删除之前已经删除了它,另一个,我的应用程序将抛出异常。
@davethieben,这正是我需要的信息。我没有意识到删除时父子关系仍然存在。
解决方案是在获取记录时使用 select for update 来锁定行,这样在你做之前没有其他东西可以删除它。
b
beny23

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

但是,为了更好地了解导致问题的原因,请尝试以下操作:

在您的休眠配置中,将 hibernate.show_sql 设置为 true。这应该向您显示执行并导致问题的 SQL。

将 Spring 和 Hibernate 的日志级别设置为 DEBUG,这将再次让您更好地了解导致问题的行。

创建一个单元测试来复制问题,而无需在 Spring 中配置事务管理器。这应该让您更好地了解有问题的代码行。

希望有帮助。


hibernate.show_sql > 我宁愿建议将日志类别 org.hibernate.SQL 级别设置为 DEBUG。这样你就不需要仅仅为了记录而修改休眠配置。
“show_sql”的使用可能非常冗长且在生产中不实用。为此,我在 BatchingBatcher 类的第 74 行修改了 catch 子句,以使用 ps.toString() 打印语句,以仅包含有问题的语句。
R
Rēda Biramanē

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

如果显式设置 Id 属性的值,则会导致上述错误。检查此项以避免此错误。或者当您在映射文件中提到字段生成器 =“本机”或“增量”并且在您的数据库中映射的表不是 auto_incremented 解决方案:转到您的数据库并更新您的表以设置 auto_increment


绝对正确。这一定是正确的答案。谢谢@Rēda Biramanē
但是,您可以将 ID 值设置为“0”,然后 Hibernate 会随意覆盖它
谢谢!不知道为什么这不是排名最高的答案。
S
Sergio Lema

就我而言,我在两个类似的情况下遇到了这个例外:

在使用@Transactional 注释的方法中,我调用了另一个服务(响应时间很长)。该方法更新了实体的一些属性(在该方法之后,实体仍然存在于数据库中)。如果用户在第二次退出事务方法时请求了两次该方法(因为他认为第一次不起作用),Hibernate 会尝试更新从事务开始时已经改变其状态的实体。当 Hibernate 搜索处于某个状态的实体时,发现相同的实体但已被第一个请求更改,它会抛出异常,因为它无法更新实体。这就像 GIT 中的冲突。

我有更新实体的自动请求(用于监控平台)(以及几秒钟后的手动回滚)。但是这个平台已经被一个测试团队使用了。当测试人员在与自动请求相同的实体中执行测试时(在相同的百分之一毫秒内),我得到了异常。与前一种情况一样,当退出第二个事务时,先前获取的实体已经改变。

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

解决方案:要解决此问题,您必须使用 Hibernate LockMode 来找到最适合您要求的那个。


您好,感谢您的有用回答。您能否告知您最终选择了哪种 LockMode 以消除该错误。我面临着类似的问题,当一个 API 在不同的毫秒内被连续命中时,由于用户无意中点击了两次。悲观锁定会损害性能;所以会有兴趣知道你最终的目标是什么?
我已经很久没有遇到这个问题了,我不记得我已经实施的确切解决方案,但它是 LockMode.READLockMode.OPTIMISTIC
c
cSn

当我为某些对象(测试)分配特定的 ID,然后我试图将它们保存在数据库中时,这种情况偶然发生在我身上。问题是在数据库中有一个设置对象 ID 的特定策略。如果您有休眠级别的策略,请不要分配 ID。


J
Julius

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


V
Vlad Mihalcea

Hibernate 5.4.1 和 HHH-12878 问题

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

创建 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 或更高版本才能从这项改进中受益。


M
Mr. TA

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

SET NOCOUNT ON;

这个答案让我朝着正确的方向前进。我正在使用一个带有触发器的旧数据库 - 幸运的是我正在用代码替换触发器所做的事情,所以我可以删除它。
+1 那也是我的问题。我使用的是 postgresql,因此需要使用 @SQLInsert 注释来关闭行数检查:technology-ebay.de/the-teams/mobile-de/blog/…
设置 NOCOUNT 关闭*
你把这个注释放在哪里了?在存储库中?
@RomainDereux 将其添加为导致问题的触发器的第一行。
P
ParagFlume

我面临同样的问题。该代码在测试环境中工作。但它在暂存环境中不起作用。

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 表中的键。

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


嗨@ParagFlume,当我尝试从我的数据库中选择一个对象时,我得到了同样的错误“批量更新从更新返回了意外的行数:0 实际行数:0 预期:1”,问题只存在于任何生产中,你有任何关于如何解决这个问题的想法,我处于危急情况。问候 !
我认为查询在 db 中没有找到任何记录,它期望在 db 中找到一条记录,它正在尝试更新。如果数据库上确实存在记录,您可以手动检查/查询浏览器。
是的记录存在,但我的问题是为什么休眠尝试更新,我只是使用选择服务从数据库中获取对象!
可能是您正在尝试更改对象的状态。会话仍然开放。
我如何检查这个行为,因为我不是开发服务的人......
M
Marcel

当您尝试UPDATE 主键 时也会发生这种情况。


C
Community

正如 Julius 所说,当更新发生在其子级被删除的对象上时,就会发生这种情况。 (可能是因为需要更新整个父对象,有时我们更喜欢删除子对象并将它们重新插入父对象(新的,旧的无关紧要)以及父亲可能对任何其他对象进行的任何其他更新它的其他普通字段)所以...为了使它起作用,通过调用 childrenList.clear() 删除子项(在事务中)(不要循环遍历子项并使用一些 childDAO.delete(childrenList.get(i).delete())) 删除每个子项并在父亲对象的一侧。然后更新父亲(fatherDAO.update(father))。(对每个父亲对象重复)结果是孩子们与父亲的链接被剥离,然后他们被框架作为孤儿移除.


A
Amicable

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

在 master 的 hibernate hbm 映射文件中,对于具有集合类型排列的对象,添加了 cascade="save-update" 并且它工作正常。

如果没有这个,默认情况下,hibernate 会尝试更新不存在的记录,并通过这样做来代替插入。


B
Bryan Pugh

出现此错误的另一种方法是,如果您的集合中有一个空项目。


M
MadukaJ

我遇到了同样的问题,我验证这可能是由于自动递增主键而发生的。为了解决这个问题,不要用数据集插入自动增量值。插入没有主键的数据。


C
CodeSlave

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

session.clear();

R
Roberto Rodriguez

这也发生在我身上,因为我的 id 为 Long,并且我从视图中接收到值 0,当我尝试保存在数据库中时出现此错误,然后我通过将 id 设置为 null 来修复它。


A
Amit Mishra

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

我使用了 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();
    }

G
Gibado

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

J
John Lopez

当您将 JSF 托管 Bean 声明为

@RequestScoped;

当你应该声明为

@SessionScoped;

问候;


S
Soggiorno

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


K
Kuldeep Verma

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

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

我将 hibernateTemplate 用于 CRUD 操作。


L
Luis Teijon

在阅读了所有答案后,没有找到任何人谈论休眠的逆属性。

在我看来,您还应该在您的关系映射中验证是否适当地设置了反向关键字。创建反向关键字来定义维护关系的所有者是哪一方。更新和插入的过程因该属性而异。

假设我们有两个表:

主体表、中间表

具有一对多的关系。休眠映射类分别是 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();

这对我有用,但是您可以建议一些版本以改进解决方案。这样我们都会学习。


S
Sandeep Khantwal

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

事实上,我们在一个休眠会话中删除了该行两次。之前我们使用的是 ojdbc6 lib,这在这个版本中是可以的。

但是当我们升级到odjc7或ojdbc8时,两次删除记录是抛出异常。我们的代码中存在错误,我们删除了两次,但这在 ojdbc6 中并不明显。

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

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

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


K
Kiran Maharjan

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


a
abhi

如果您使用本机 sql 查询更改数据集中的某些内容,但会话缓存中存在相同数据集的持久对象,则会发生这种情况。使用 session.evict(yourObject);


J
Justinas Jakavonis

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


D
Draken

案例之一

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

k
kamel2005

我正面临这个异常,并且休眠运行良好。我尝试使用 pgAdmin 手动插入一条记录,这里问题变得清晰了。 SQL 插入查询返回 0 插入。并且有一个触发函数会导致此问题,因为它返回 null。所以我只需将其设置为返回新的。最后我解决了这个问题。

希望对任何身体都有帮助。


J
Javasick

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

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


G
Ganesh Giri

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅