Hibernate-批更新从更新返回意外行数:0实际行数:0预期值:1


140

我收到以下休眠错误。我能够确定导致问题的功能。不幸的是,函数中有多个数据库调用。由于休眠在事务结束时刷新会话,因此我找不到导致问题的行。下面提到的休眠错误看起来像是一般错误。甚至都没有提到哪个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。我已经更新了我的电子邮件。
Sujee 2010年

我也有同样的问题。这不是大问题,因为这种情况很少发生。使用show_sql不切实际,因为重现此行为需要数百万个事务。但是,由于这种情况在我运行的系统测试(包含成千上万笔交易)中反复发生,因此我怀疑是有特定原因的。
daniel_or_else

我在尝试更新没有更改的行时遇到了这个问题。如果没有差异,请不要更新。
fifman

Answers:


62

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

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

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

希望有帮助。


21
hibernate.show_sql>我宁愿建议将日志类别org.hibernate.SQL级别设置为DEBUG。这样,您无需仅为了记录就修改休眠配置。
Thierry

“ show_sql”的使用可能非常冗长,并且在生产中不切实际。为此,我修改了BatchingBatcher类第74行的catch子句,以使用ps.toString()打印该语句以仅包含出现问题的语句。
Orden

71

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


12
当我从父子关系中删除一个孩子,保存父对象(这会删除该孩子),然后尝试手动删除该孩子时,我遇到了这个问题。
戴夫·蒂本

这解决了我的问题。该记录不存在,并且我的服务正在调用updateAll()方法,而实际上需要调用createOrUpdateAll()方法。谢谢。
Mital Pritmani 2014年

3
那么该如何解决呢?如果我得到一条记录,然后将其删除;但是如果系统在删除之前已经删除了它,则另一个应用程序将引发异常。
斯托尼2014年

@davethieben,这正是我需要的信息。我没有意识到父子关系在删除后仍然存在。
2015年

解决方案是在获取记录以锁定行时使用select for update,因此其他任何操作都无法删除。
马修·

54

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

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


2
绝对正确。这一定是正确的答案。感谢@RēdaBiramanē–
Kuldeep Verma

1
但是,您可以将ID值设置为“ 0”,然后Hibernate可以随时覆盖它
-forresthopkinsa

1
谢谢!不知道为什么这不是排名最高的答案。
斯图尔特·麦金太尔

18

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


15

就我而言,我在两个类似的情况下遇到了此异常:

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

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

解决方案:要解决该问题,您将必须使用Hibernate LockMode来找到最适合您要求的一种。


嗨,谢谢您的帮助。您能否告知您最终选择了哪种LockMode来消除该错误。我遇到了类似的问题,因为用户无意中单击了两次,导致API在不同毫秒内连续被命中。悲观锁定会损害性能;这样是否有兴趣知道您最终的追求?
Madhur Bhaiya,

1
我遇到问题已经有很长时间了,我不太清楚我已经提出的确切解决方案,但是LockMode.READ还是LockMode.OPTIMISTIC
Sergio Lema

13

我刚遇到此问题,发现我正在删除一条记录,然后尝试在Hibernate事务中对其进行更新。


9

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

SET NOCOUNT ON;

1
这个答案使我朝着正确的方向前进。我正在使用具有触发器的旧式数据库-幸运的是,我用代码替换了触发器,所以我可以删除它。
S. Baggy 2014年

2
+1那也是我的问题。:我使用PostgreSQL数据库,所以需要使用@SQLInsert注释关掉行计数检查technology-ebay.de/the-teams/mobile-de/blog/...
s1mm0t

SET NOCOUNT OFF *
G. Ciardini

7

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

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 //表示未找到要更新的记录
:0 //表示未找到记录,因此未更新
:1 //表示期望至少有1条记录在db表中。

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


@ParagFlume,您好,我遇到相同的错误:“批处理更新从更新返回意外行数:0实际行数:0预期:1”当我尝试从数据库中选择一个对象时,问题仅存在于任何生产中,您有任何问题吗?关于如何解决这个问题的想法,我处境危急。问候 !
詹姆斯

我认为查询未在db中找到任何记录,它期望在db中尝试更新的一条记录。您可以手动/查询浏览器检查记录是否确实存在于数据库中。
ParagFlume

是的,记录存在,但是我的问题是为何休眠尝试更新,我只是在使用选择服务从数据库中获取对象!
詹姆斯,

可能是您正在尝试更改对象的状态。会话仍处于打开状态。
ParagFlume

我如何检查这个行为,因为我不是开发该服务的人...
James

5

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


5

我在一对多关系中遇到了这个问题。

在用于主机的休眠hbm映射文件中,对于具有设置类型排列的对象,添加了 cascade="save-update"它并且效果很好。

没有这个,默认情况下,hibernate会尝试更新不存在的记录,并且这样做会插入。


4

当您尝试UPDATE输入PRIMARY KEY时,也会发生这种情况。


3

我遇到了同样的问题,并且我验证了由于自动递增主键可能发生的情况。要解决此问题,请不要在数据集中插入自动增量值。插入没有主键的数据。



2

当您尝试删除同一对象,然后再次更新同一对象时,会发生这种情况

session.clear();



1

当我手动开始并在标记为的方法内部提交事务时,我遇到了这个问题@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;
            }
        }
    }
}

1

当您将JSF Managed Bean声明为

@RequestScoped;

当你应该声明为

@SessionScoped;

问候;


1

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


1

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

实际上,在我的应用程序中,我正在获取一个数据库对象进行更新。在更新其值时,我还错误地更新了其ID,然后继续进行更新并遇到了所述错误。

我正在使用hibernateTemplateCRUD操作。


1

看完所有答案后,没有人再谈论冬眠的逆属性。

我认为您还应该在关系映射中验证是否正确设置了反向关键字。创建Inverse关键字以定义维护关系的所有者。更新和插入的过程根据此属性而有所不同。

假设我们有两个表:

主体表中间表

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

因此,Principal类具有SET 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”,则意味着“中级”类是关系所有者,因此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();

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


1

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

实际上,我们在一个休眠会话中两次删除了该行。之前我们使用的是ojdbc6 lib,在此版本中没关系。

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

我们能够用以下代码重现:

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

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


1

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

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

1

Hibernate 5.4.1和HHH-12878问题

在Hibernate 5.4.1之前,开放式锁定失败异常(例如StaleStateExceptionOptimisticLockException)不包含失败的语句。

HHH-12878的创建问题,以提高休眠,从而引发乐观锁定异常的情况下,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
    );
}

测试时间

我在BatchingOptimisticLockingTest高性能Java持久性GitHub存储库中创建了,以演示新行为的工作方式。

首先,我们将定义一个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=?

因此,您需要升级到Hibernate 5.4.1或更高版本才能受益于此改进。


0

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



0

一种情况

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

0

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

希望对任何人有帮助。


0

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

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



0

在我的情况下,数据库存在问题,因为其中一个存储过程正在消耗所有CPU,从而导致较高的数据库响应时间。一旦被杀死,问题就解决了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.