首页 > 解决方案 > 第二笔交易覆盖了第一笔交易的更改

问题描述

我在一个项目中工作,我们将 Spring Data Rest 与 HATEOAS、Spring JPA 和 Hibernate 一起使用,目前我们遇到了一个无法解决的奇怪问题:

  1. Angular 前端将两个 HTTP 请求传输到后端。两个请求都希望在同一行的不同列中添加关系(因为 HATEOAS 必须在两个请求中完成)。
  2. 创建了两个事务(A 和 B),Hibernate 在每个事务中执行选择以获取有问题的行。
  3. Hibernate 获取将在每个事务中链接的实体。
  4. Hibernate 在事务 A 中更新一次选定的行。事务 B 不久之后更新该行并将事务 A 中设置的值重置为事务开始时执行的初始选择的值。所以我们丢失了事务 A 的所有信息。

问题是这似乎不是确定性的。它有时会发生,然后在接下来的十次中正常工作。

我们无法通过添加锁定来解决这个问题(乐观锁定只会导致ObjectOptimisticLockingFailureException事务 B),悲观锁定也没有效果。我们正在为实体使用 DynamicUpdate。我们尝试在交易上设置传播,但这并没有改变任何东西。现在我们没有想法,谷歌搜索没有产生任何结果。

应该更新的实体如下所示:

@AllArgsConstructor
@RequiredArgsConstructor
@NoArgsConstructor
@Data
@JsonIgnoreProperties(value = { "history" }, allowGetters = true)
@DynamicUpdate
@SelectBeforeUpdate
@Entity
@Table(name = "parameters")
public class Parameter
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "parameter_id", unique = true)
    private Long id;

    @NonNull
    @Column(name = "name", nullable = false, unique = true)
    private String key;

    @ManyToOne(fetch = FetchType.EAGER,
            cascade = {
                    CascadeType.REFRESH,
                    CascadeType.MERGE,
                    CascadeType.PERSIST,
                    CascadeType.DETACH
            }
    )
    private Component component;

    @OneToOne(fetch = FetchType.EAGER,
            cascade = {
                    CascadeType.REFRESH,
                    CascadeType.MERGE,
                    CascadeType.PERSIST,
                    CascadeType.DETACH
            }
    )
    private ParameterTypes type;

    @OneToMany(
            mappedBy = "parameter",
            fetch = FetchType.LAZY,
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    @OrderBy("history_creation_timestamp DESC")
    private List<History> history = new LinkedList<>();

    @CreationTimestamp
    @Column(name = "parameter_creation_timestamp")
    private LocalDateTime creationTimestamp;

    @UpdateTimestamp
    @Column(name = "parameter_update_timestamp")
    private LocalDateTime updateTimestamp;
}

component并且字段type应该更新。

如果我们能确保两个请求都正确更新行,或者事务 B 等待事务 A 完成并重新选择处于更新状态的行,那就太好了。

我们非常感谢任何帮助,因为我们在过去三天内无法找到可行的解决方案。我们很乐意提供任何其他信息。谢谢大家。

前端

我们使用angular4-hal和 Angular 7 与 Spring 后端进行通信。该调用相当简单,如下所示:

saveParameter(): void {
    this.editMode = false;
    const saveObservs: Observable<any>[] = [];
    if (this.componentControl.dirty) {
    saveObservs.push(this.parameter.addRelation('component', this.componentControl.value));
    }

    if (this.typeControl.dirty) {
    saveObservs.push(this.parameter.addRelation('type', this.typeControl.value));
    }

    forkJoin(
    saveObservs
    ).pipe(
    catchError(err => of(err))
    ).subscribe(val => console.log(val));
}

componentControl并且typeControl都是FormControl实例。

addRelation函数如下所示:

Resource.prototype.addRelation = function (relation, resource) {
    if (this.existRelationLink(relation)) {
        var header = ResourceHelper.headers.append('Content-Type', 'text/uri-list');
        return ResourceHelper.getHttp().put(ResourceHelper.getProxy(this.getRelationLinkHref(relation)), resource._links.self.href, { headers: header });
    }
    else {
        return observableThrowError('no relation found');
    }
};

函数的上下文可以在这里找到

标签: hibernatespring-data-jpaspring-dataspring-data-restspring-transactions

解决方案


不幸的是,我不了解 Angular 7,但我有一种saveObservs.push异步工作的感觉。这意味着有时第二个请求在第一个请求完成之前到达 API ,从数据库加载未修改的记录,然后只修改第二个引用将其写回。

所以第二次推送应该等待第一次推送的结果。

但是,如果您尝试同时从两个客户端修改同一个对象,则无法解决此问题。

您应该version向实体添加属性,如果您尝试使用过时版本修改对象,Spring Data Rest 将自动响应错误。

  @Version
  private Long version;

推荐阅读