Вторая транзакция отменяет изменения из первой транзакции - PullRequest
0 голосов
/ 22 февраля 2019

Я работал в проекте, где мы используем Spring Data Rest с HATEOAS, Spring JPA и Hibernate, и в настоящее время мы сталкиваемся со странной проблемой, которую нам не удалось исправить:

  1. Angular frontendпередает два HTTP-запроса на серверную часть.Оба запроса хотят добавить отношение в разные столбцы в одной строке (из-за HATEOAS это должно быть сделано в двух запросах).
  2. Создаются две транзакции (A и B), и Hibernate выполняет выбор в каждой транзакции.чтобы получить строку, о которой идет речь.
  3. Hibernate получает права, которые будут связаны в каждой транзакции.
  4. Hibernate обновляет выбранную строку один раз в транзакции A. Транзакция B вскоре обновляет строку и сбрасывает еезначение, установленное в транзакции A, возвращается к значению из начального выбора, который был выполнен в начале транзакции.Таким образом, мы теряем всю информацию о транзакции A.

Проблема в том, что это не кажется детерминированным.Это иногда случается, а затем нормально работает следующие десять раз.

Мы не смогли исправить это, добавив блокировку (оптимистическая блокировка просто приводит к ObjectOptimisticLockingFailureException в транзакции B), и пессимистическая блокировка также не имела никакого эффекта.,Мы используем DynamicUpdate для объекта.Мы попытались установить распространение транзакций, но это ничего не изменило.Сейчас у нас нет идей, и поиск в Google не дал никаких результатов.

Право, которое должно быть обновлено, выглядит следующим образом:

@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');
    }
};

А контекст функции можно найти здесь

1 Ответ

0 голосов
/ 22 февраля 2019

К сожалению, я не знаю Angular 7, но у меня такое ощущение, что saveObservs.push работает асинхронно.Это означает, что иногда 2-й запрос достигает API до того, как 1-й был завершен, загружает неизмененную запись из БД, а затем записывает ее обратно только с 2-й измененной ссылкой.

Таким образом, 2-е нажатие должно ждать результата 1-го.

Однако это не решит проблему, если вы попытаетесь изменить один и тот же объект из двух клиентов одновременно.

Необходимо добавить свойство version к сущности, и Spring Data Rest автоматически ответит ошибкой, если вы попытаетесь изменить объект с устаревшей версией.

  @Version
  private Long version;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...