Hibernate i Cascade Delete

0

Cześć. Posiadam dwie encje, będące ze sobą w relacji OneToMany. Chciałbym usuwając rodzica usuwać także dzieci, jednak SQL ma z tym jakiś problem

java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`pickmeup_database`.`offered_ride`, CONSTRAINT `offered_ride_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`))

Schemat bazy

title

OfferedRide.class

@Entity
@Table(name="offered_ride")
public class OfferedRide {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_offered_ride")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private int id_offered_ride;

    @Column(name="date_of_ride")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private Date date_of_ride;

    @Column(name="time_of_ride")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private Time time_of_ride;

    @Column(name="number_of_free_seats")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private int number_of_free_seats;

    @Column(name="ride_category")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private int ride_category;

    @Column(name="from_where")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private String from_where;

    @Column(name="to_where")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private String to_where;

    @Column(name="user_comment")
    @JsonView({View.User.class, View.ActivationCode.class, View.OfferedRide.class, View.Seat.class})
    private String user_comment;

    @ManyToOne(cascade= {CascadeType.PERSIST, CascadeType.MERGE,
            CascadeType.DETACH, CascadeType.REFRESH})
    @JoinColumn(name="id_user")
    @JsonView({View.OfferedRide.class, View.Seat.class})
    private User user;

    @OneToMany(mappedBy="offeredRide",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<Seat> listOfSeats;

    public OfferedRide() {

    }

User.class

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_user")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private int id_user;

    @OneToOne(cascade = {CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.PERSIST,
            CascadeType.REFRESH})
    @JoinColumn(name = "id_activation_code")
    @JsonView({View.User.class})
    private ActivationCode activationCode;

    @Column(name = "user_name")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_name;

    @Column(name = "user_surname")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_surname;

    @Column(name = "user_email")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_email;

    @Column(name = "user_password")
    @JsonView({View.ActivationCode.class, View.User.class})
    private String user_password;

    @Column(name = "user_phone_number")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_phone_number;

    @Column(name = "user_car")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_car;

    @Column(name = "user_description")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class})
    private String user_description;

    @Column(name = "is_active")
    @JsonView({View.ActivationCode.class, View.User.class})
    private boolean is_active;

    @OneToMany(mappedBy="user",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<OfferedRide> listOfUsersOfferedRides;

    @OneToMany(mappedBy="user",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<Seat> listOfUserSeats;

    public User() {

    }

Metoda usuwająca

    @Override
    public Response deleteUserById(int theID) {
        Session session = entityManager.unwrap(Session.class);
        Response response = new Response();
        User user;
        try {
            user = session.get(User.class, theID);
        } catch (NoResultException ex) {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
            return response;
        }
        if(user != null){
            Query query = session.createQuery("DELETE FROM User user WHERE user.id_user = " + theID);
            query.executeUpdate();
            response.setSuccesful(true);
            response.setMsg("User with ID " + theID + " successfully deleted");
        } else {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
        }
        return response;
    }

Próbowałem wielu rozwiązań, stackoverflow itp, ale nic nie działa. Cały czas SQL pluje się, że probuje usunąć obiekt który jest kluczem obcym innego obiektu, mimo, że zaznaczone jest wyraźnie CascadeType.ALL

Czy ktoś może ma jakiś pomysł ?

1

Wydaje mi się, że bulk delete nie wywołuje kaskady. Musisz użyć remove() na sesji/EntityManagerze.

1

Cascade delete jest obsługiwany nie przez Hiberante ale na poziomie bazy danych. Czy na pewno masz w schemacie bazy danych odpowiednio ustawione CASCADE DELETE na kluczach obcych (powinno być coś w stylu ON DELETE CASCADE)?

0

@0xmarcin:

Oj raczej nie. Tak wygląda mój plik tworzący bazę danych

CREATE TABLE `activation_code` (
  `id_activation_code` int(11) NOT NULL,
  `serial_number` varchar(15) COLLATE utf8mb4_polish_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;

-- --------------------------------------------------------

--
-- Struktura tabeli dla tabeli `offered_ride`
--

CREATE TABLE `offered_ride` (
  `id_offered_ride` int(11) NOT NULL,
  `id_user` int(11) NOT NULL,
  `date_of_ride` date NOT NULL,
  `time_of_ride` time NOT NULL,
  `number_of_free_seats` int(2) NOT NULL,
  `ride_category` int(1) NOT NULL,
  `from_where` varchar(100) COLLATE utf8mb4_polish_ci NOT NULL,
  `to_where` varchar(100) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_comment` varchar(250) COLLATE utf8mb4_polish_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;

-- --------------------------------------------------------

--
-- Struktura tabeli dla tabeli `seat`
--

CREATE TABLE `seat` (
  `id_seat` int(11) NOT NULL,
  `id_offered_ride` int(11) NOT NULL,
  `id_user` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;

-- --------------------------------------------------------

--
-- Struktura tabeli dla tabeli `user`
--

CREATE TABLE `user` (
  `id_user` int(11) NOT NULL,
  `id_activation_code` int(11) NOT NULL,
  `user_name` varchar(20) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_surname` varchar(30) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_email` varchar(30) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_password` varchar(30) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_phone_number` varchar(15) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_car` varchar(100) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_description` varchar(250) COLLATE utf8mb4_polish_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;

-- --------------------------------------------------------

--
-- Struktura tabeli dla tabeli `wanted_ride`
--

CREATE TABLE `wanted_ride` (
  `id_wanted_ride` int(11) NOT NULL,
  `id_user` int(11) NOT NULL,
  `date_of_ride` date NOT NULL,
  `time_of_ride` time NOT NULL,
  `ride_category` int(2) NOT NULL,
  `from_where` varchar(100) COLLATE utf8mb4_polish_ci NOT NULL,
  `to_where` varchar(100) COLLATE utf8mb4_polish_ci NOT NULL,
  `user_comment` varchar(250) COLLATE utf8mb4_polish_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;

--
-- Indeksy dla zrzutów tabel
--

--
-- Indeksy dla tabeli `activation_code`
--
ALTER TABLE `activation_code`
  ADD PRIMARY KEY (`id_activation_code`);

--
-- Indeksy dla tabeli `offered_ride`
--
ALTER TABLE `offered_ride`
  ADD PRIMARY KEY (`id_offered_ride`),
  ADD KEY `id_user` (`id_user`);

--
-- Indeksy dla tabeli `seat`
--
ALTER TABLE `seat`
  ADD PRIMARY KEY (`id_seat`),
  ADD KEY `id_wanted_ride` (`id_offered_ride`),
  ADD KEY `id_user` (`id_user`);

--
-- Indeksy dla tabeli `user`
--
ALTER TABLE `user`
  ADD PRIMARY KEY (`id_user`),
  ADD KEY `id_activation_code` (`id_activation_code`);

--
-- Indeksy dla tabeli `wanted_ride`
--
ALTER TABLE `wanted_ride`
  ADD PRIMARY KEY (`id_wanted_ride`),
  ADD KEY `id_user` (`id_user`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT dla tabeli `activation_code`
--
ALTER TABLE `activation_code`
  MODIFY `id_activation_code` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT dla tabeli `offered_ride`
--
ALTER TABLE `offered_ride`
  MODIFY `id_offered_ride` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT dla tabeli `seat`
--
ALTER TABLE `seat`
  MODIFY `id_seat` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT dla tabeli `user`
--
ALTER TABLE `user`
  MODIFY `id_user` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT dla tabeli `wanted_ride`
--
ALTER TABLE `wanted_ride`
  MODIFY `id_wanted_ride` int(11) NOT NULL AUTO_INCREMENT;

--
-- Ograniczenia dla zrzutów tabel
--

--
-- Ograniczenia dla tabeli `offered_ride`
--
ALTER TABLE `offered_ride`
  ADD CONSTRAINT `offered_ride_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`);

--
-- Ograniczenia dla tabeli `seat`
--
ALTER TABLE `seat`
  ADD CONSTRAINT `seat_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`),
  ADD CONSTRAINT `seat_ibfk_2` FOREIGN KEY (`id_offered_ride`) REFERENCES `offered_ride` (`id_offered_ride`);

--
-- Ograniczenia dla tabeli `user`
--
ALTER TABLE `user`
  ADD CONSTRAINT `user_ibfk_1` FOREIGN KEY (`id_activation_code`) REFERENCES `activation_code` (`id_activation_code`);

--
-- Ograniczenia dla tabeli `wanted_ride`
--
ALTER TABLE `wanted_ride`
  ADD CONSTRAINT `wanted_ride_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`);
COMMIT;
0

@Charles_Ray: Właśnie próbowałem używać session.remove, to w ogóle miałem problem z usunięciem obiektu, nawet nie powiązanego z niczym

1

Spróbuj ustawić orphanRemoval = true

1

Zgadzam się z @Charles_Ray, o ile nic z biblii JPA nie zapomniałem. Delete na EM music być

0

@scibi92: Próbowałem na 3 sposoby

        if(user != null){
//            Query query = session.createQuery("DELETE FROM User user WHERE user.id_user = " + theID);
//            query.executeUpdate();
//            session.remove(user);
//            session.delete(user);

Niestety ostatnie 2 linijki nie daja rezultatu. Nie usuwa obiektu

1

IMO jakk korzystasz z JPA to bądź konsekwentny i korzystaj z EntityManagerza

0

@scibi92: Nic nie działa, nawet entityManager. Jedyna opcja, żeby usunąć, to hql i DELETE FROM User. Reszta nie przynosi rezultatów. Wiesz może gdzie może być błąd ?

1

Na moje oko w załączonym schemacie bazy danych NIE MA cascade delete na kluczu obcym. Jeżeli ta baza danych tworzona jest ręcznie to należy dodać te brakujące kaskady. Jeżeli przez Hibernate to należy przegenerować.

Na mój rozum: Usuwasz za pomocą HQL'a a więc bez ładowania encji do pamięci. W tym wypadku Hibernate zmieni to na prosty DELETE SQL'owy i wyśle do bazy, oczekując że jest tam odpowiednia kaskada ustawiona.

Wygeneruj sobie bazę z tego mapowania z Hibernate i porównaj z tym co masz, to powinno Ci uwidocznić różnice.

Warto też sobie na etapie debugowania włączyć logowanie SQLi w Hibernate, przynajmniej będzie widać od razu co ORM chce zrobić.... (https://stackoverflow.com/questions/2536829/hibernate-show-real-sql)

EDIT: Jak słusznie zauważył @Charles_Ray ta odpowiedź nie ma sensu jeżeli mamy ustawione w @OneToMany(cascade=REMOVE) kaskady, bo wtedy Hibernate załaduje i tak powiązane encje do pamięci a następnie pousuwa je (https://thorben-janssen.com/avoid-cascadetype-delete-many-assocations/).
Natomiast odpowiedź jest słuszna jeżeli zrezygnujemy z użycia kaskad (np. ze względu na wydajność i żeby uniknąć problemu select N+1). Wtedy musimy albo sami usuwać powiązane encje albo zrzucić tą odpowiedzialność na bazę....

0

@RezyserKinaAkcji: jaki błąd leci na remove() ?

0

@Charles_Ray: Żaden. Wszystko gra, tylko obiekt nie jest usuwany z bazy danych

0

Pokaz kod. Masz transakcje otwarta?

0

@Charles_Ray:

UserDAO.class

    @Override
    public Response deleteUserById(int theID) {
        Session session = entityManager.unwrap(Session.class);
        Response response = new Response();
        User user;
        try {
            user = session.get(User.class, theID);
        } catch (NoResultException ex) {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
            return response;
        }
        if(user != null){
            Query query = session.createQuery("DELETE FROM User user WHERE user.id_user = " + theID);
            query.executeUpdate();
            response.setSuccesful(true);
            response.setMsg("User with ID " + theID + " successfully deleted");
        } else {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
        }
        return response;
    }

UserService.class

    @Override
    @Transactional
    public Response deleteUserById(int theID) {
        return userDAO.deleteUserById(theID);
    }

UserRestController.class

    @DeleteMapping("/user/delete/{theID}")
    public Response deleteUserById(@PathVariable int theID){
        return userService.deleteUserById(theID);
    }
0

Być może ugryzł Ciebie też błąd opisany tutaj: https://www.baeldung.com/delete-with-hibernate#deletion-using-the-entity-manager

Spróbuj usunąć dla przykładu bez wcześniejszego wczytywania encji użytkownika.

0

@jarekr000000:

    @Override
    public Response deleteUserById(int theID) {
        Session session = entityManager.unwrap(Session.class);
        Response response = new Response();
        User user;
        try {
            user = session.get(User.class, theID);
        } catch (NoResultException ex) {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
            return response;
        }
        if(user != null){
//            Query query = session.createQuery("DELETE FROM User user WHERE user.id_user = " + theID);
//            query.executeUpdate();
            entityManager.remove(user);
            response.setSuccesful(true);
            response.setMsg("User with ID " + theID + " successfully deleted");
        } else {
            response.setMsg("User with ID " + theID + " not found");
            response.setSuccesful(false);
        }
        return response;
    }

title

title

To samo dzieje się, kiedy remove wywołuje na session, nie na entityManagerze. W konsoli żadnego błędu ze strony Springa ani Hibernate'a

1

Jaka to wersja hibernate?
Trzeba buga zgłosić

Ale możesz jeszcze na wszelki wypadek zrobić disconnect / connect do bazy danych (z tego narzędzia) i sprawdzić czy na pewno nie znikło.

0

@jarekr000000:

2020-11-12 15:51:28.776  INFO 19080 --- [         task-1] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.22.Final

User.class

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_user")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private int id_user;

    @OneToOne(cascade = {CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.PERSIST,
            CascadeType.REFRESH})
    @JoinColumn(name = "id_activation_code")
    @JsonView({View.User.class})
    private ActivationCode activationCode;

    @Column(name = "user_name")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_name;

    @Column(name = "user_surname")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_surname;

    @Column(name = "user_email")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_email;

    @Column(name = "user_password")
    @JsonView({View.ActivationCode.class, View.User.class})
    private String user_password;

    @Column(name = "user_phone_number")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_phone_number;

    @Column(name = "user_car")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_car;

    @Column(name = "user_description")
    @JsonView({View.ActivationCode.class, View.User.class, View.OfferedRide.class, View.Seat.class, View.WantedRide.class})
    private String user_description;

    @Column(name = "is_active")
    @JsonView({View.ActivationCode.class, View.User.class})
    private boolean is_active;

    @OneToMany(mappedBy="user",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<OfferedRide> listOfUsersOfferedRides;

    @OneToMany(mappedBy="user",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<Seat> listOfUserSeats;

    @OneToMany(mappedBy="user",
            cascade= {CascadeType.ALL})
    @JsonView({View.User.class})
    private List<WantedRide> listOfWantedRides;



    public User() {

    }

Zrobiłem disconnecta, nadal obiekt jest w bazie. W zasadzie działa tylko query "DELETE FROM User...."

7

@RezyserKinaAkcji
Problem jest tu w klasie User

  @OneToOne(cascade = {CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.PERSIST,
            CascadeType.REFRESH})
    @JoinColumn(name = "id_activation_code")
    @JsonView({View.User.class})
private ActivationCode activationCode;

zamień to na

  @OneToOne(cascade = {CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.PERSIST,
            CascadeType.REMOVE, //To trzeba było dodać
            CascadeType.REFRESH})
    @JoinColumn(name = "id_activation_code")
    @JsonView({View.User.class})
private ActivationCode activationCode;

i zadziała zwykłe remove na session.

Ciekawostki:
faktycznie, żadnych błędów EntityManger czy Session nie rzucał. Po prostu remove było ignorowane ( obiekt był cichaczem przywracany w czasie flush).
żaden DELETE statement nawet nie leciał do bazy danych.

ActivationCode miał OneToOne do User i zgaduje (bo nie chce mi się głębiej wyjaśniać), że przez to niejako remove kończył się przywróceniem obiektu jeszcze raz (bo ActivationCode nadal trzyma). Usunięcie kaskad od strony ActivationCode też pomaga na problem.

A teraz proszę o wyjasnienia ekspertów od JPA, Hibernate. Chciałbym zrozumieć, że to logiczne.

Bo jak dla mnie to JPA/Hibernate to g**no.. (no jakiś komunikat o problemie by się przydał, bo śledzenie po wszystkich powiązaniach, co do cholery trzymie obiekt podczas flush to niezła bryndza).

0

@jarekr000000: Dzięki wielkie. Działa. Tylko tutaj teraz pojawia się problem, ze kasowanie Usera kasuje również rekord w tabeli ActivationCode, a tego chciałem uniknąć. Chciałem, by kasowanie Usera zwalniało ActivationCode, który później można by przypisać innemu Userowi, a nie kasowało go całkowicie.

Tak czy siak to kosmetyka i da się z tym żyć :P Dzięki wielkie za znalezienie rozwiązania !

1

To przed remove(user) ustaw user = null na tym ActivationCode i powinno być ok.

1

@jarekr000000: Ahh, no tak. Chyba nawet tak to było rozwiązane na kursie. No jasne. Dzięki wielkie. I swoją drogą pozdrawiam Pana Czarodzieja, po którego rekomendacjach trafiłem na to zacne forum ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1