Hibernate可以使用MySQL的“ON DUPLICATE KEY UPDATE”语法吗?

2022-09-01 09:00:27

MySQL支持“”语法,允许您“盲目地”插入到数据库中,并回退到更新现有记录(如果存在)。INSERT ... ON DUPLICATE KEY UPDATE ...

当您希望快速隔离事务并且要更新的值取决于数据库中已有的值时,这非常有用。

作为一个人为的例子,假设你想计算一个故事在博客上被观看的次数。使用此语法执行此操作的一种方法可能是:

 INSERT INTO story_count (id, view_count) VALUES (12345, 1)
 ON DUPLICATE KEY UPDATE set view_count = view_count + 1

这将比开始交易更有效率,更有效,并处理当新故事出现在首页时发生的不可避免的异常。

我们如何用Hibernate做同样的事情,或者完成同样的目标?

首先,Hibernate 的 HQL 解析器将引发异常,因为它不理解特定于数据库的关键字。事实上,HQL 不喜欢任何显式插入,除非它是一个 “”。INSERT ... SELECT ....

其次,休眠将 SQL 限制为仅选择。休眠将引发异常,如果您尝试调用 。session.createSQLQuery("sql").executeUpdate()

第三,在这种情况下,Hibernate's不符合要求。您的测试将通过,但是如果您每秒有多个访问者,则会出现生产故障。saveOrUpdate

我真的必须颠覆冬眠吗?


答案 1

你看过Hibernate @SQLInsert Annotation吗?

@Entity
@Table(name="story_count")
@SQLInsert(sql="INSERT INTO story_count(id, view_count) VALUES (?, ?)
 ON DUPLICATE KEY UPDATE view_count = view_count + 1" )
public class StoryCount

答案 2

这是一个古老的问题,但我遇到了类似的问题,并认为我会添加到这个主题中。我需要将日志添加到现有的无状态会话审核日志编写器。现有实现使用的是无状态会话,因为标准会话实现的缓存行为是不必要的开销,我们不希望休眠侦听器触发审核日志写入。此实现是关于在没有交互的情况下实现尽可能高的写入性能。

但是,新的日志类型需要使用插入-else-update 类型的行为,其中我们打算使用事务时间更新现有日志条目作为“标记”行为类型。在 StatelessSession 中,没有提供 saveOrUpdate(),因此我们需要手动实现 insert-else-update。

鉴于这些要求:

您可以使用 mysql “插入 ...在重复键更新“行为中,通过休眠持久对象的自定义 sql 插入。您可以通过注释(如上面的答案)或通过 sql 插入实体(休眠 xml 映射)来定义自定义 sql-insert 子句,例如:

<class name="SearchAuditLog" table="search_audit_log" persister="com.marin.msdb.vo.SearchAuditLog$UpsertEntityPersister">

    <composite-id name="LogKey" class="SearchAuditLog$LogKey">
        <key-property
            name="clientId"
            column="client_id"
            type="long"
        />
        <key-property
            name="objectType"
            column="object_type"
            type="int"
        />
        <key-property
            name="objectId"
            column="object_id"
        />
    </composite-id>
    <property
        name="transactionTime"
        column="transaction_time"
        type="timestamp"
        not-null="true"
    />
    <!--  the ordering of the properties is intentional and explicit in the upsert sql below  -->
    <sql-insert><![CDATA[
        insert into search_audit_log (transaction_time, client_id, object_type, object_id)
        values (?,?,?,?) ON DUPLICATE KEY UPDATE transaction_time=now()
        ]]>
    </sql-insert>

最初的海报特别询问了MySQL。当我使用mysql实现insert-else-update行为时,当sql的“更新路径”执行时,我得到了异常。具体来说,mysql报告在仅更新1行时更改了2行(表面上是因为现有行被删除并插入了新行)。有关特定功能的更多详细信息,请参阅此问题。

因此,当更新返回 2 倍于受休眠影响的行数时,休眠会引发 BatchedTooManyRowsAffectedException,将回滚事务并引发异常。即使您要捕获异常并处理它,到那时事务也已经回滚。

经过一番挖掘,我发现这是休眠使用的实体持久化器的问题。在我的情况下,休眠是使用 SingleTableEntityPersister,它定义了一个期望,即更新的行数应与批处理操作中定义的行数匹配。

使此行为正常工作所需的最后一个调整是定义自定义持久化器(如上面的 xml 映射所示)。在这个例子中,我们所要做的就是扩展 SingleTableEntityPersister 并“覆盖”插入期望。例如,我刚刚将此静态类附加到持久性对象上,并将其定义为休眠映射中的自定义持久化器:

public static class UpsertEntityPersister extends SingleTableEntityPersister {

    public UpsertEntityPersister(PersistentClass arg0, EntityRegionAccessStrategy arg1, SessionFactoryImplementor arg2, Mapping arg3) throws HibernateException {
        super(arg0, arg1, arg2, arg3);
        this.insertResultCheckStyles[0] = ExecuteUpdateResultCheckStyle.NONE;
    }

}

我花了很长时间才通过休眠代码找到这个 - 我无法在网上找到任何主题的解决方案。


推荐