postgresql - Kotlin JPA 一对多 @ElementCollection 尝试保存重复项导致违反约束
问题描述
我定义了以下实体(简化)...
@Entity(name = "metrics")
data class MetricsEntity(
val name: String,
// ... other properties omitted for clarity
@ElementCollection(fetch = FetchType.EAGER)
@MapKeyColumn(name = "event_name")
@Column(name = "event_count")
@CollectionTable(name = "metric_event", joinColumns = [JoinColumn(name = "metrics_id")])
val events: MutableMap<String, Int>,
)
这里的想法是,对于指标表中的每个条目,我们记录事件计数,最终得到一个包含这样条目的表......
metrics_id | event_name | event_count
------------+-----------------------------------+-------------
15647624 | Launched | 1
15647624 | Registration_successful | 10
15647624 | Registration_failed | 2
15647624 | History_viewed | 1
在代码中,我们使用类似这样的方式加载指标实体......
val metrics = metricsRepository.findByProperties(properties)
...获得一个指标。此处的选择标准已被简化,但足以说明我们从该查询中获得了一个指标实例。这里的存储库定义为...
interface MetricsRepository : CrudRepository<MetricsEntity, Long> {
...
}
现在我们要么更新事件映射以使用以下代码添加一个新计数,即增加一个现有计数...
metrics.events[eventName] = (it.events[eventName] ?: 0) + 1
metricsRepository.save(it)
这在绝大多数情况下都有效,但保存调用时不时会在上表中引发称为 metric_event_constraint 的约束冲突,该约束被定义为......
ALTER TABLE metric_event ADD CONSTRAINT metric_event_constraint UNIQUE (metrics_id, name);
这似乎表明当行已经存在时,保存操作正在保存新行。查看日志表明我在尝试修改相同计数的多个线程之间发生冲突......
08:58:18.466 INFO 3 --- [TaskExecutor-11] incr count for event Launched, count 1
08:58:18.487 INFO 3 --- [cTaskExecutor-4] incr count for event Launched, count 1
08:58:18.618 ERROR 3 --- [cTaskExecutor-4] incr count failed for request
08:58:24.623 INFO 3 --- [TaskExecutor-94] incr count for event Launched, count 2
08:59:14.951 INFO 3 --- [askExecutor-126] incr count for event Launched, count 3
...这里第一个事件起作用并增加计数,第二个事件失败并且没有(发现约束违规),第三个和第四个工作正常。当我们想要 4 个时,总数为 3。在我看来,第一个和第二个事件发生了冲突。
所以问题首先是你认为这个总结是否正确?其次,我如何使它工作;)?我的假设是我需要锁定度量实体,那么我将如何使用使用的 crud 存储库和实体类的框架来解决这个问题?
这背后的数据库是 Postgres。
问候,
标记
解决方案
所以问题首先是你认为这个总结是否正确?
这是很有可能的。
我如何使它工作;)?
您可以在获取时锁定实体(使用EntityManager.find()
if 直接交互的第二个参数EntityManger
,或@Lock
Spring Data 存储库方法上的注释。对于您的场景,我猜您需要乐观锁定,这意味着您需要添加实体的一个@Version
字段(如果乐观锁失败,您可能还想用悲观锁重试,但如果冲突很少,纯粹的乐观锁可能是要走的路)。
当然,如果您希望两次修改尝试都成功,则需要通过重试操作来捕获和处理锁定异常(我相信 Spring repos 会将其包装成 a ObjectOptimisticLockingFailureException
,但您必须检查)。
推荐阅读
- kotlin - 等待超时输入的协程
- javascript - 在 django 上使用 blob 和 arraybuffer 加载图像不起作用
- excel - 如何设置 Excel 按钮来执行两个特定的 VBA 宏?
- sql-server - 自动添加的主键尝试插入 NULL 的值
- javascript - Appmaker Typeerror 建议 _equals 方法是我无法调用的属性
- android - 如何在模拟器中连接自定义wifi网络?
- google-apps-script - 尝试使用 GmailApp.sendEmail - 获取缺少的访问令牌进行授权
- sql - 选择带有修改值和通配符的语句
- python - Matplotlib 在 3d 表面上绘制轮廓
- algorithm - 阿姆斯壮数逻辑