Удаление каскадного распространения вызывает сбой ограничения внешнего ключа - PullRequest
1 голос
/ 29 мая 2019

Я использую Spring Boot (2.1.0.RELEASE) с Spring Data JPA. База данных MySQL.

У меня есть проблема с цепным каскадным удалением.

У меня есть следующая модель:

Database model

Я не использую @ManyToMany, потому что мне нужны дополнительные поля в сгенерированных таблицах, поэтому мои сущности выглядят следующим образом (ненужные свойства были удалены):

@Audited
@Entity
@Table(name = "request")
public class Request {

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private Resource resource;

}


@Audited
@Entity
@Table(name = "resource")
public class Resource {

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "resource")
    private Set<ResourceArticle> resourceArticles;

}

@Audited
@Entity
@Table(name = "resource_article")
public class ResourceArticle {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "article_id")
    private Article article;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "resource_id")
    private Resource resource;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "resourceArticle")
    private Set<ResourceArticleOption> options;

}

@Audited
@Entity
@Table(name = "resource_article_option")
public class ResourceArticleOption {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "option_id")
    private Option option;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "resource_article_id")
    private ResourceArticle resourceArticle;

}

Тогда от моего менеджера я просто выдаю запрос delete, расширяющий CrudRepository:

/* Repositroy */
public interface RequestRepository extends CrudRepository<Request, Long> {
}

/* Manager */
@Transactional
@Component("requestMgr")
public class RequestManager {

    @Autowired
    RequestRepository requestRepository;

    public void delete(Request request) {
        requestRepository.delete(request);
    }

}

/* Viewmodel */
public class RequestVm {

    @WireVariable
    private RequestManager requestMgr;

    public void deleteRequest(Request req) {
        requestMgr.delete(req);
    }

}

Ошибка:

Причина: java.sql.SQLException: Невозможно удалить или обновить родительский элемент строка: ограничение внешнего ключа не выполнено (my_db. resource_article, ОГРАНИЧЕНИЕ FK5wqvprkwx05fb5hgt6w9h7nbk ИНОСТРАННЫЙ КЛЮЧ (resource_id) ССЫЛКИ resource (id))

Вывод при включении трассировки:

delete from request where id=?
binding parameter [1] as [BIGINT] - [24]
delete from resource where id=?
binding parameter [1] as [BIGINT] - [71]
SQL Error: 1451, SQLState: 23000
(conn=30) Cannot delete or update a parent row: a foreign key constraint fails (`my_db`.`resource_article`, CONSTRAINT `FK5wqvprkwx05fb5hgt6w9h7nbk` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`))

Странно, что пытается удалить в порядке request> resource> resource_article> resource_article_option?

I нужно CascadeType.ALL, поскольку я хочу PERSIST и DELETE.

Я могу разорвать цепочку, установив ссылку на null перед удалением, но, конечно, это приведет к появлению в базе данных потерянных записей.

Какова лучшая стратегия здесь?

1 Ответ

0 голосов
/ 11 июня 2019

Я заставил его работать, удалив в каждом отношении сущность, которая не является владельцем отношения.

Каскадное удаление в @ManyToMany отношениях применяется не только к таблице ссылок, но и к другой стороне.отношения также.

Это хорошо известная проблема при удалении из @ManyToMany ассоциаций (это хорошо объяснено здесь , например), но я не обратил внимания, потому что мойаннотации - это не прямые @ManyToMany, а двойные @OneToMany ассоциации, и я, хотя JPA каскадно удаляю, как в простых @OneToMany отношениях, но, похоже, я ошибался.

Я использовал @preRemove для очисткиОтношение перед удалением запроса:

В ResourceArticle:

@PreRemove
public void preRemove() {
    article.getResourceArticles().remove(this);
}

В ResourceArticleOption:

@PreRemove
public void preRemove() {
    option.getResourceArticleOptions().remove(this);
}

Тогда все работает отлично.

...