第二个事务覆盖第一个事务的更改
Second transaction overrides changes from first transaction
我在一个项目中工作,我们使用 Spring Data Rest with HATEOAS、Spring JPA 和 Hibernate,目前我们遇到了一个我们无法修复的奇怪问题:
- Angular 前端向后端传输两个 HTTP 请求。两个请求都希望在同一行的不同列中添加关系(因为 HATEOAS,这必须在两个请求中完成)。
- 创建了两个事务(A 和 B),Hibernate 在每个事务中执行 select 以获取相关行。
- Hibernate 获取将在每个事务中链接的实体。
- Hibernate 在事务 A 中更新 selected 行一次。事务 B 不久之后更新该行并将事务 A 中设置的值重置回执行的初始 select 的值在交易开始时。所以我们正在丢失交易 A 的所有信息。
问题是这似乎不是确定性的。它只是有时会发生,然后在接下来的十次中都可以正常工作。
我们无法通过添加锁定来解决此问题(乐观锁定只会导致事务 B 中的 ObjectOptimisticLockingFailureException
)并且悲观锁定也没有任何效果。我们正在为实体使用 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 完成并重新select使行处于更新状态,那就太好了。
我们非常感谢任何帮助,因为我们在过去三天无法找到有效的解决方案。我们很乐意提供任何其他信息。
提前谢谢大家。
前端
我们使用 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');
}
};
并且可以找到函数的上下文here
不幸的是我不知道Angular 7,但我感觉saveObservs.push
是异步工作的。
这意味着有时第二个请求在 API 第一个请求完成之前到达 API ,从数据库加载 unmodified 记录,并且然后只修改第二个引用将其写回。
所以第二次推送应该等待第一次的结果。
但是,如果您尝试同时从两个客户端修改同一个对象,则无法解决问题。
您应该将 version
属性 添加到实体中,如果您尝试修改具有过时版本的对象,Spring Data Rest 将自动响应并返回错误。
@Version
private Long version;
我在一个项目中工作,我们使用 Spring Data Rest with HATEOAS、Spring JPA 和 Hibernate,目前我们遇到了一个我们无法修复的奇怪问题:
- Angular 前端向后端传输两个 HTTP 请求。两个请求都希望在同一行的不同列中添加关系(因为 HATEOAS,这必须在两个请求中完成)。
- 创建了两个事务(A 和 B),Hibernate 在每个事务中执行 select 以获取相关行。
- Hibernate 获取将在每个事务中链接的实体。
- Hibernate 在事务 A 中更新 selected 行一次。事务 B 不久之后更新该行并将事务 A 中设置的值重置回执行的初始 select 的值在交易开始时。所以我们正在丢失交易 A 的所有信息。
问题是这似乎不是确定性的。它只是有时会发生,然后在接下来的十次中都可以正常工作。
我们无法通过添加锁定来解决此问题(乐观锁定只会导致事务 B 中的 ObjectOptimisticLockingFailureException
)并且悲观锁定也没有任何效果。我们正在为实体使用 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 完成并重新select使行处于更新状态,那就太好了。
我们非常感谢任何帮助,因为我们在过去三天无法找到有效的解决方案。我们很乐意提供任何其他信息。 提前谢谢大家。
前端
我们使用 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');
}
};
并且可以找到函数的上下文here
不幸的是我不知道Angular 7,但我感觉saveObservs.push
是异步工作的。
这意味着有时第二个请求在 API 第一个请求完成之前到达 API ,从数据库加载 unmodified 记录,并且然后只修改第二个引用将其写回。
所以第二次推送应该等待第一次的结果。
但是,如果您尝试同时从两个客户端修改同一个对象,则无法解决问题。
您应该将 version
属性 添加到实体中,如果您尝试修改具有过时版本的对象,Spring Data Rest 将自动响应并返回错误。
@Version
private Long version;