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

时间:2010-04-30 08:11:15

标签: java hibernate

我得到以下hibernate错误。我能够识别导致问题的功能。不幸的是,函数中有几个DB调用。我无法找到导致问题的行,因为hibernate会在事务结束时刷新会话。下面提到的hibernate错误看起来像是一般错误。它甚至没有提到哪个Bean导致了这个问题。有人熟悉这个hibernate错误吗?

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)

34 个答案:

答案 0 :(得分:58)

我删除了一个根本不存在的记录时遇到了同样的异常。因此,检查您正在更新/删除的记录实际存在于DB

答案 1 :(得分:51)

如果您的交易没有代码和映射,则几乎无法调查此问题。

但是,为了更好地处理导致问题的原因,请尝试以下方法:

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

希望有所帮助。

答案 2 :(得分:44)

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

如果明确设置Id属性的值,则会导致上述错误。检查这一点以避免此错误。 要么 当你在映射文件中提到字段生成器=“native”或“incremental”并且在你的DATABASE中映射的表不是auto_incremented时,会显示错误 解决方案:转到数据库并更新表以设置auto_increment

答案 3 :(得分:17)

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

答案 4 :(得分:9)

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

SET NOCOUNT ON;

答案 5 :(得分:7)

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

答案 6 :(得分:7)

就我而言,我在两个类似的案例中遇到了这个例外:

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

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

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

答案 7 :(得分:5)

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

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

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

答案 8 :(得分:4)

正如Julius所述,当对其子项被删除的对象发生更新时会发生这种情况。 (可能是因为需要对整个父对象进行更新,有时我们更愿意删除这些孩子并将其重新插入父亲(新的,旧的无关紧要)以及父亲可以对任何其他任何更新其他平原领域) 所以...为了使这个工作,通过调用childrenList.clear()删除子项(在事务中)(不要循环遍历子项并删除每个childDAO.delete(childrenList.get(i).delete()))并设置 父对象一侧的@OneToMany(cascade = CascadeType.XXX ,orphanRemoval=true)。然后更新父亲(fatherDAO.update(父亲))。 (对每个父对象重复)结果是孩子们剥离了他们父亲的链接,然后被框架作为孤儿被移除。

答案 9 :(得分:3)

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

答案 10 :(得分:3)

当您尝试for /f "tokens=1* delims=_" %A in ('dir /B *_*.txt') Do @Echo Ren "%A_%B" "%A%~xB" PRIMARY KEY 时也会发生这种情况。

答案 11 :(得分:2)

发生此错误的另一种方法是,如果集合中有空项目。

答案 12 :(得分:2)

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

答案 13 :(得分:1)

当您将JSF Managed Bean声明为

时会发生这种情况
@RequestScoped;

何时应声明为

@SessionScoped;

问候;

答案 14 :(得分:1)

休眠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
    );
}

测试时间

我在高性能Java持久性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或更高版本才能受益于此改进。

答案 15 :(得分:1)

我也遇到了同样的挑战。在我的情况下,我使用hibernateTemplate更新了一个甚至不存在的对象。

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

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

答案 16 :(得分:1)

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

答案 17 :(得分: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;
            }
        }
    }
}

答案 18 :(得分:1)

当您尝试删除同一个对象然后再次更新同一个对象时会发生删除

session.clear();

答案 19 :(得分:0)

在我的情况下,这是因为在插入原因之前触发了 trigger (实际上,这意味着使用时间戳将一个大表拆分为多个表),然后返回 null 。因此,当我使用springboot jpa save()函数时遇到了这个问题。

除了将触发器更改为SET NOCOUNT ON;以外,还可以。 TA 上面提到的,该解决方案也可以使用本地查询

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

答案 20 :(得分:0)

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

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

答案 21 :(得分:0)

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

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

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

我们能够用这段代码进行复制:

Error   CS0123  No overload for 'CustomTypeConversionErrorMessageProvider' matches delegate 'ModelBinderErrorMessageProvider'

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

答案 22 :(得分:0)

几种调试此错误的方法:

  1. 按照接受的答案中的建议-打开show sql。
  2. 我发现在休眠sql中设置ID时存在一些问题。
  3. 发现我缺少@GeneratedValue(strategy = GenerationType.IDENTITY)

答案 23 :(得分:0)

在阅读完所有答案之后,没有人发现任何人谈论hibernate的反向属性。

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

假设我们有两个表:

principal_table middle_table

关系一对多。 hiberntate映射类分别为 Principal Middle

所以 Principal 类有一组对象。 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类将 NOT UPDATE 关系。

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

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

这对我有用,你可以推荐一些版本以改善解决方案。这样我们都将学习。

答案 24 :(得分:0)

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

答案 25 :(得分:0)

实际上,当我没有将对象存储为引用变量时,它就发生在我身上。在实体类中。喜欢这段代码:    ses.get(InsurancePolicy.class, 101);之后,我将对象存储在实体的引用变量中,这样就解决了问题。 policy=(InsurancePolicy)ses.get(InsurancePolicy.class, 101); 之后,我更新了对象并且工作正常。

答案 26 :(得分:0)

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

答案 27 :(得分:0)

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

答案 28 :(得分:0)

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

希望有助于任何身体。

答案 29 :(得分:0)

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

使用Id(x => x.Id, "id").GeneratedBy.**Identity**();

解决了问题

答案 30 :(得分: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();

答案 31 :(得分:0)

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

答案 32 :(得分:0)

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

答案 33 :(得分:-1)

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