这是一个古老的问题,但我遇到了类似的问题,并认为我会添加到这个主题中。我需要将日志添加到现有的无状态会话审核日志编写器。现有实现使用的是无状态会话,因为标准会话实现的缓存行为是不必要的开销,我们不希望休眠侦听器触发审核日志写入。此实现是关于在没有交互的情况下实现尽可能高的写入性能。
但是,新的日志类型需要使用插入-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;
}
}
我花了很长时间才通过休眠代码找到这个 - 我无法在网上找到任何主题的解决方案。