Hibernate: Как обновить родительскую строку после удаления принадлежащего объекта - PullRequest
0 голосов
/ 29 апреля 2020

Итак, у меня есть 4 объекта (Person, Car, Garage, Image)

отношение между Person и Car - oneToMany (у одного человека может быть много машин) при использовании 'session.remove (somePerson)' I есть исключение: «Невозможно удалить или обновить родительскую строку: ограничение внешнего ключа не выполнено». Однако я могу использовать 'session.remove (someCar)', и это работает. Я предполагаю, что моя проблема заключается в том, что когда человек удален, Hibernate не может обновить внешний ключ в Cars, и я предполагаю, что это как-то связано с каскадированием.

Просто чтобы уточнить: когда я удаляю (somePerson) желаемый результат is: somePerson было удалено. его автомобили не будут удалены! (будет иметь нулевое значение в столбце владельца).

Автомобиль. java

package com.example;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "cars")
public class Car {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String licensePlate;
    private double price;
    private int year;

    @ManyToOne(fetch = FetchType.LAZY)
    @Cascade(CascadeType.SAVE_UPDATE)
    @JoinColumn(name = "owner_id")
    private Person owner;

    @OneToOne(fetch = FetchType.LAZY)
    @Cascade(CascadeType.ALL)
    private Image image;

    @ManyToMany
    @Cascade({CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
            name = "cars_garages",
            joinColumns = @JoinColumn(name = "car_id"),
            inverseJoinColumns = @JoinColumn(name = "garage_id")
    )
    private List<Garage> garageList;

    //GROUP C'tors
    public Car(String licensePlate, double price, int year) {
        this();
        this.licensePlate = licensePlate;
        this.price = price;
        this.year = year;
    }
    public Car() {
        garageList = new ArrayList<>();
    }

    //GROUP adders
    public void addGarage(Garage garage) {
        if (!garageList.contains(garage))
            garageList.add(garage);

        if (!garage.getCarList().contains(this))
            garage.getCarList().add(this);
    }

    //GROUP setters and getters
    public int getId() {
        return id;
    }

    public String getLicensePlate() {
        return licensePlate;
    }
    public void setLicensePlate(String licensePlate) {
        this.licensePlate = licensePlate;
    }

    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }

    public Image getImage() {
        return image;
    }
    public void setImage(Image image) {
        this.image = image;

        if (image.getCar() != this)
            image.setCar(this);
    }

    public Person getOwner() {
        return owner;
    }
    public void setOwner(Person owner) {

        this.owner = owner;
        if (!owner.getCarList().contains(this))
            owner.getCarList().add(this);
    }

    public List<Garage> getGarageList() {
        return garageList;
    }
    public void setGarageList(List<Garage> garageList) {
        this.garageList = garageList;
    }

    @Override
    public String toString() {

        StringBuilder string = new StringBuilder("Car: ID = " + id + ", license plate = " + licensePlate +
                ", price = " + price + ", year = " + year + "\nowner details:\n\t" + "ID: " + owner.getId()
                + ", full name = " + owner.getFirstName() + " " + owner.getLastName() + ", email address = " +
                owner.getEmailAddress() + ", password = " + owner.getPassword() + "\n"
                + "This car can get service at the following addresses:\n");
        for (Garage garage : garageList)
            string.append("\t").append(garage.getAddress()).append("\n");

        return string.toString();
    }
}

Персона. java

package com.example;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "persons")
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String firstName, lastName, emailAddress, password;

    @OneToMany(
            //cascade = CascadeType.ALL,
            fetch = FetchType.LAZY,
            mappedBy = "owner"
    )
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DETACH})
    private List<Car> carList;

    @ManyToMany//(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @Cascade({CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
            name = "persons_garages",
            joinColumns = @JoinColumn(name = "person_id"),
            inverseJoinColumns = @JoinColumn(name = "garage_id")
    )
    private List<Garage> garageList;

    //GROUP C'tors
    public Person(String firstName, String lastName, String password, String emailAddress) {
        this();
        this.firstName = firstName;
        this.lastName = lastName;
        this.password = password;
        this.emailAddress = emailAddress;
    }
    public Person() {
        this.carList = new ArrayList<>();
        this.garageList = new ArrayList<>();
    }

    //GROUP adders
    public void addCar(Car car) {

        if (!carList.contains(car))
        {
            carList.add(car);
            car.setOwner(this);
        }
    }

    public void addGarage(Garage garage) {

        if (!garageList.contains(garage))
            garageList.add(garage);

        if (!garage.getOwners().contains(this))
            garage.getOwners().add(this);
    }

    public void removeCar(Car car) {
        carList.remove(car);
    }

    //GROUP setters and getters
    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmailAddress() {
        return emailAddress;
    }
    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public List<Car> getCarList() {
        return carList;
    }
    public void setCarList(List<Car> carList) {
        this.carList = carList;
    }

    public List<Garage> getGarageList() {
        return garageList;
    }
    public void setGarageList(List<Garage> garageList) {
        this.garageList = garageList;
    }
}


Приложение. java

package com.example;

import java.util.List;
import java.util.Random;
import java.util.logging.Level;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Parent;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class App {

    private static final int NUM_OF_PERSONS = 5;
    private static final int NUM_OF_CARS = 5;
    private static final int NUM_OF_GARAGES = 2;
    private static final int NUM_OF_IMAGES = 5;
    private static Session session;

    public static void main(String[] args) {
        try
        {
            SessionFactory sessionFactory = getSessionFactory();
            session = sessionFactory.openSession();
            session.beginTransaction();
            generateCars();
            generatePersons();
            generateGarages();
            generateImages();
            connectEntities();

            session.clear();
            List<Person> personList = getAllOfType(Person.class);
            session.remove(personList.get(0));
            session.flush();        // Exception is being thrown here

            session.getTransaction().commit();

            printAllOfType(Garage.class);
            printAllOfType(Car.class);
        }
        catch (Exception exception)
        {
            if (session != null)
                session.getTransaction().rollback();

            System.err.println("An error occurred, changes have been rolled back.");
            exception.printStackTrace();
        } finally
        {
            assert session != null;
            session.close();
            session.getSessionFactory().close();
        }
    }

    private static SessionFactory getSessionFactory() throws HibernateException {

        java.util.logging.Logger.getLogger("org.hibernate").setLevel(Level.OFF);
        Configuration configuration = new Configuration();
        configuration.addAnnotatedClass(Car.class);
        configuration.addAnnotatedClass(Person.class);
        configuration.addAnnotatedClass(Garage.class);
        configuration.addAnnotatedClass(Image.class);
        ServiceRegistry serviceRegistry =
                new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        return configuration.buildSessionFactory(serviceRegistry);
    }

    private static void generateCars() {
        Random random = new Random();
        for (int i = 0; i < NUM_OF_CARS; i++)
        {
            Car car = new Car("MOO-" + random.nextInt(999999), 100000,
                    2000 + random.nextInt(19));
            session.save(car);
        }
        session.flush();
    }

    private static void generatePersons() {

        Random random = new Random();
        String[] firstNames = {"Avi", "Dan", "John", "Didi", "Avihu"};
        String[] lastNames = {"Hemmo", "Bilzerian", "Snow", "Harrari", "Medina"};
        for (int i = 0; i < NUM_OF_PERSONS; i++)
        {
            Person person = new Person(firstNames[i % firstNames.length], lastNames[i % lastNames.length],
                    String.valueOf(random.nextInt(99999)),
                    firstNames[i % firstNames.length] + lastNames[i % lastNames.length] + "@fake.com");
            session.save(person);
        }
        session.flush();
    }

    private static void generateGarages() {

        String[] addresses = {"1 1st st, New York, NY", "5 5th st, New York, NY"};
        for (int i = 0; i < NUM_OF_GARAGES; i++)
        {
            Garage garage = new Garage(addresses[i % addresses.length]);
            session.save(garage);
        }
        session.flush();
    }

    private static void generateImages() {
        for (int i = 0; i < NUM_OF_IMAGES; i++)
            session.save(new Image());
        session.flush();
    }

    private static <T> List<T> getAllOfType(Class<T> objectType) {
        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(objectType);
        query.from(objectType);
        return session.createQuery(query).getResultList();
    }

    private static <T> void printAllOfType(Class<T> objectType) {
        List<T> tList = getAllOfType(objectType);
        for (T object : tList)
            System.out.println(object);
    }

    private static void connectEntities() {

        List<Person> persons = getAllOfType(Person.class);
        List<Car> cars = getAllOfType(Car.class);
        List<Garage> garages = getAllOfType(Garage.class);
        List<Image> images = getAllOfType(Image.class);

        // connect cars with owners & cars with garages
        for (int i = 0; i < cars.size(); i++)
        {
            cars.get(i).setOwner(persons.get(i % persons.size()));
            cars.get(i).addGarage(garages.get(i % garages.size()));
            cars.get(i).setImage(images.get(i % images.size()));
            session.update(cars.get(i));
        }
        session.flush();

        // connect persons with garages
        for (int i = 0; i < persons.size(); i++)
        {
            persons.get(i).addGarage(garages.get(i % garages.size()));
            session.update(persons.get(i));
        }
        session.flush();
    }

}

Журнал ошибок

An error occurred, changes have been rolled back.
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1356)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1339)
    at com.example.App.main(App.java:42)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3551)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3810)
    at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:124)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:723)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1352)
    ... 2 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`lab5`.`cars`, CONSTRAINT `FK82ccqb3p17md157c33e2qerq9` FOREIGN KEY (`owner_id`) REFERENCES `persons` (`id`))
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
    ... 14 more

1 Ответ

0 голосов
/ 29 апреля 2020

Собственная сторона отношений - Автомобиль, а не Персона, поэтому вы не можете удалить Автомобиль, не удалив его Персона с текущим кодом. Вы можете изменить способ выполнения операции удаления, сначала удалив ассоциацию (установите для поля owner в Car значение null и сохраните сущность Car перед удалением Person), либо вы можете изменить способ построения отношений с помощью третья таблица, которая ее обрабатывает (когда у меня возникла та же проблема, я выбрал первое решение).

Если вы следуете первому способу, вы должны выполнить всю операцию (задав для полей owner значение * 1006). * и удаление автомобиля) внутри транзакции, или вы можете оказаться в несогласованном состоянии, если что-то пойдет не так.

...