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

Posted

技术标签:

【中文标题】休眠 - 批量更新从更新返回意外的行数:0 实际行数:0 预期:1【英文标题】:Hibernate - Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1 【发布时间】:2011-02-14 03:32:51 【问题描述】:

我得到以下休眠错误。我能够识别导致问题的功能。不幸的是,函数中有几个 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 是不切实际的,因为重现此行为需要数百万个事务。但是,由于这种情况在我运行的系统测试期间反复发生(有数以亿计的事务),我怀疑有一个特定的原因。 我在尝试更新有 NO 更改的行时遇到了这个问题。如果没有差异,请不要更新。 【参考方案1】:

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

【讨论】:

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

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

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

在您的休眠配置中,将 hibernate.show_sql 设置为 true。这应该会向您显示执行并导致问题的 SQL。 将 Spring 和 Hibernate 的日志级别设置为 DEBUG,这将再次让您更好地了解导致问题的行。 创建一个单元测试来复制问题,而无需在 Spring 中配置事务管理器。这应该可以让您更好地了解有问题的代码行。

希望对您有所帮助。

【讨论】:

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

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

如果显式设置Id属性的值,会导致上面的错误。检查此项以避免此错误。 要么 当您在映射文件中提到字段 generator="native" 或“incremental”并且在您的 DATABASE 中映射的表不是 auto_incremented 时,这是错误显示 解决方案:转到您的 DATABASE 并更新您的表以设置 auto_increment

【讨论】:

完全正确。这一定是正确的答案。谢谢@Rēda Biramanē 但是,你可以将 ID 值设置为 '0',然后 Hibernate 会随意覆盖它 谢谢!不知道为什么这不是排名最高的答案。【参考方案4】:

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

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

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

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

【讨论】:

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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,以演示新行为的工作原理。

首先,我们将定义一个Post 实体,该实体定义了一个@Version 属性,因此启用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 或更高版本才能从这项改进中受益。

【讨论】:

【参考方案8】:

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

SET NOCOUNT ON;

【讨论】:

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

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

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 中找到一条记录,它正在尝试更新。您可以手动/查询浏览器检查数据库上是否确实存在记录。 是的记录存在,但我的问题是为什么休眠尝试更新,我只是使用选择服务从数据库中获取对象! 可能是你试图改变对象的状态。会话仍然打开。 我如何检查这个行为,因为我不是开发服务的人......【参考方案10】:

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

【讨论】:

【参考方案11】:

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

【讨论】:

【参考方案12】:

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

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

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

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

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

【讨论】:

【参考方案15】:

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

session.clear();

【讨论】:

【参考方案16】:

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

【讨论】:

【参考方案17】:

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

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

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

当您将 JSF 托管 Bean 声明为时会发生这种情况

@RequestScoped;

什么时候你应该声明为

@SessionScoped;

问候;

【讨论】:

【参考方案20】:

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

【讨论】:

【参考方案21】:

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

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

我正在使用 hibernateTemplate 进行 CRUD 操作。

【讨论】:

【参考方案22】:

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

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

假设我们有两个表:

principal_tablemiddle_table

具有一对多的关系。休眠映射类分别是 PrincipalMiddle

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

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

【讨论】:

【参考方案23】:

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

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

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

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

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

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

【讨论】:

【参考方案24】:

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

【讨论】:

【参考方案25】:

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

【讨论】:

【参考方案26】:

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

【讨论】:

【参考方案27】:

其中一个案例

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

【讨论】:

【参考方案28】:

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

希望对任何人都有帮助。

【讨论】:

【参考方案29】:

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

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

【讨论】:

【参考方案30】:

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

【讨论】:

以上是关于休眠 - 批量更新从更新返回意外的行数:0 实际行数:0 预期:1的主要内容,如果未能解决你的问题,请参考以下文章

StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1

如何解决“批量更新从更新返回意外的行数;实际行数:0;预期:1”问题?

HIbernate“StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1”

org.hibernate.StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1

org.hibernate.StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期:1

org.hibernate.StaleStateException:批量更新从更新 [0] 返回了意外的行数;实际行数:0;预期的