Reputation: 988
I working in a project where we use Spring Data Rest with HATEOAS, Spring JPA and Hibernate and at the moment we are experiencing a weird problem we were not able to fix:
The problem is that this does not seem to be deterministic. It just happens sometimes and then works fine the following ten times.
We were not able to fix this by adding locking (optimistic locking just leads to an ObjectOptimisticLockingFailureException
in transaction B) and pessimistic locking had no effect either. We are using DynamicUpdate for the entity. We tried setting propagation on the transactions but that did not change anything.
Right now we are out of ideas and googling did not yield any results.
The entitiy that should be updated looks as follows:
@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;
}
And the fields component
and type
should be updated.
It would be great if we could assure that both requests update the row correctly or transaction B waits for transaction A to finish and re-selects the row in its updated state.
We greatly appreciate any assistance since we were unable to find a working solution in the last three days. We will gladly provide any additional information. Thank you all in advance.
Frontend
We are using angular4-hal with Angular 7 to communicate with the Spring backend. The call is rather simple and looks as follows:
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
and typeControl
are both FormControl
instances.
The addRelation
function looks like this:
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');
}
};
And the context of the function can be found here
Upvotes: 0
Views: 258
Reputation: 3423
Unfortunately I don't know Angular 7, but I have a feeling that saveObservs.push
works asynchronously.
Which means that sometimes the 2nd request reaches the API before the 1st one was finished, load the unmodified record from the DB, and then write it back with only the 2nd reference modified.
So the 2nd push should wait for the result of the 1st one.
However it won't solve the case if you try to modify the same object from two client at the same time.
You should add version
property to the entity and Spring Data Rest will automatically respond with an error if you try to modify an object with an obsolete version.
@Version
private Long version;
Upvotes: 1