为啥在更新事务期间休眠调用删除?

Posted

技术标签:

【中文标题】为啥在更新事务期间休眠调用删除?【英文标题】:Why is hibernate calling delete during an update transaction?为什么在更新事务期间休眠调用删除? 【发布时间】:2017-02-13 23:24:14 【问题描述】:

我在我的项目日志中看到异常,我无法重现该问题。

当我尝试使用 Hibernate 更新客户端时,不时会发生这种情况(并非总是如此)。 根据日志中显示的信息,hibernate 正在对更新事务调用删除方法

知道为什么会这样吗?

日志

org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 // Is this because the client has been deleted by hibernate?
    at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:85)
    at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:70)
    at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:47)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2707)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2911) // This is called by hibernate
    at org.hibernate.action.EntityDeleteAction.execute(EntityDeleteAction.java:97)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:189)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    at com.project.dao.ClientDAO.updatetx(ClientDAO.java:138)
    at com.project.bl.ClientsBL.updateClient(ClientsBL.java:2291) // This is the method I call
    at [other non hibernate code]

ClientDAO

// ...
public class ClientDAO 
    // ...    
    public Object updatetx(Object instance) throws Exception
        Session session = getSession();
        Transaction tx = session.beginTransaction();
        session.update(instance);
        tx.commit(); // This is line ClientDAO.java:138

        return instance;
    
    // ...

客户BL

//..
public class ClientsBL 
    // ...
    public void updateClient(Client client) 
        // ...
        clientDAO.updatetx(client); // This is ClientsBL.java:2291
        // ...
    
    // ...    

编辑:添加更多映射和信息:

HBM:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.project.model.client.Client" table="CLIENT" dynamic-update="true">
        <id name="clientId" type="java.lang.Long">
            <column name="CLIENT_ID" />
            <generator class="native" />
        </id>

        <property name="countryId" type="java.lang.Long">
            <column name="COUNTRY_ID" />
        </property>

        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <!-- ... many other columns mapped in the same way -->
    </class>
</hibernate-mapping>

Client.java 只有字段+getter+setter(没有注释)

春天:

<bean id="clientsBL"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager">
        <ref bean="transactionManager" />
    </property>
    <property name="target">
        <ref bean="clientsBLTarget" />
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_SUPPORTS,-Exception</prop>
        </props>
    </property>
</bean>

<bean id="clientsBLTarget"
    class="com.project.bl.ClientsBL">
    <property name="clientDAO"><ref bean="clientDAO" /></property>
    <property name="clientDataDAO" ref="clientDataDAO" />
    <!-- ... more DAOs -->
</bean>

【问题讨论】:

您可以添加客户端实体的内容和/或 updateClient 方法的完整代码吗? 您是否正在尝试更新不再存在的实体,q.v. here? @TimBiegeleisen 我检查了我的代码,我们从不使用删除客户端。在我看来,客户端在更新期间被删除了,但我不确定 你有@OneToMany@ManyToMany的关系吗? 在休眠类 EntityDeleteAction 的构造函数处放一个断点,并检查调用堆栈为什么这个删除动作要排队。 【参考方案1】:

几周前我遇到了同样的异常,最终,我成功解决了它。异常是这样的:unexpected row count from update [0]; actual row count: 0; expected: 1

在我的案例中,这些是我如何获得该异常的阶段:

    我从 IOC 容器中“抓取”了一个 bean 我使用休眠持续\保存了该 bean。这意味着 bean 收到了一个 ID 并保存在 DB 中。 我已经删除了那个 bean。这意味着 bean 不再在 DB 上。 我再次从 IOC 容器中“抓取”了同一个 bean。这意味着我得到了一个带有 ID 的 bean,它是第一次插入时从之前获得的 ID。 我尝试在那个“抓取”的 bean 上执行 saveOrUpdate

这里的问题是发生了更新而不是插入(保存),我收到了那个异常:unexpected row count from update [0]; actual row count: 0; expected: 1

我得到它是因为 hibernate 认为他应该进行更新,因为它已经持久化了那个 bean,这就是为什么错误说 expected: 1,它最初应该插入 1 条记录。但是因为已经被删除所以找到actual row count: 0

解决方法很简单,在再次从IOC容器抓取之前,只需将ID设置为null即可。

【讨论】:

【参考方案2】:

没有实体映射很难调试,但你说你不能发布它,所以答案充其量只能是推测性的。

尽管如此,如果实体在一对一和一对多关联上具有 orphanRemoval 指令,则 Hibernate 在更新实体时可能会发出 delete 语句。不是更新的实体实例被删除,而是关联的孤儿(enable SQL logging 检查到底删除了什么)。

至于你偶尔遇到的StaleStateException,很有可能是因为一个并发事务同时修改了同一个实体实例(并删除了孤儿),所以Hibernate在内存中的状态与数据库中的状态,并且在刷新时检测到不一致。如果是这种情况,那么您必须处理对相同数据的并发更新(使用乐观和/或悲观锁等)。

【讨论】:

我这周没有时间测试答案(注意生产环境中会出现错误)。我奖励你赏金,因为你是第一个回答的人,我认为这指向了正确的方向。如果我能够重现该问题或发现有关该错误的更多信息,我将更新此帖子。【参考方案3】:

你可以在休眠中启用调试级别日志记录并检查是否正在删除某些内容。 也可能是 db 中不存在 CLIENT_ID 值,您可以在调试时检查它是否存在于第 138 行之前。

【讨论】:

以上是关于为啥在更新事务期间休眠调用删除?的主要内容,如果未能解决你的问题,请参考以下文章

事务在休眠中不回滚

为啥休眠 5.3 不支持带有 infinispan 的事务缓存

索引视图是不是在事务期间更新?

使用快照隔离防止 SQL 视图被冗长的删除/插入事务阻塞

理解片段事务期间片段的生命周期方法调用

在 READ UNCOMMITTED 事务期间使用索引导致无法获取更新锁