【问题标题】:Kotlin JPA One-To-Many @ElementCollection attempts to save duplicates resulting in constraint violationsKotlin JPA 一对多 @ElementCollection 尝试保存重复项导致违反约束
【发布时间】:2020-03-31 12:25:01
【问题描述】:

我定义了以下实体(简化)...

@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。

问候,

标记

【问题讨论】:

    标签: postgresql spring-boot jpa kotlin


    【解决方案1】:

    所以问题首先是你认为这个总结是否正确?

    这很有可能。

    我如何让它工作;)?

    您可以在获取时锁定实体(如果直接与 EntityManger 交互,则使用 EntityManager.find() 的第二个参数,或者 Spring Data 存储库方法上的 @Lock 注释。对于您的场景,我猜您会想要乐观锁定,这意味着您需要向您的实体添加一个@Version 字段(如果乐观锁定失败,您可能还想使用悲观锁重试,但如果冲突很少,纯粹的乐观锁定可能是方式去)。

    当然,如果您希望两次修改尝试都成功,则需要通过重试操作来捕获并处理锁定异常(我相信 Spring repos 会将其包装成 ObjectOptimisticLockingFailureException,但您必须查看)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-16
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      • 2014-02-23
      • 2014-12-18
      • 2019-05-03
      • 1970-01-01
      相关资源
      最近更新 更多