0

So I have 4 entities (Person, Car, Garage, Image)

the relation between Person and Car is oneToMany (one person can have many cars) when using 'session.remove(somePerson)' I have an exception: 'Cannot delete or update a parent row: a foreign key constraint fails'. I can however use 'session.remove(someCar)' and it works. I assume my problem is when a Person is removed hibernate cannot update the foreign key in Cars and I guess it has something to do with cascading..

Just to clarify: when I do remove(somePerson) my desired result is: somePerson has been removed. his Cars won't get removed! (will have null in owner column).

Car.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();
    }
}

Person.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;
    }
}


App.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();
    }

}

Error log

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
  • https://stackoverflow.com/questions/9944137/have-jpa-hibernate-to-replicate-the-on-delete-set-null-functionality – nayakam Apr 29 '20 at 09:55

1 Answers1

0

The owning side of the relationship is Car, not Person, so you cannot remove a Car without removing its Person with the current code. You could modify the way the removal operation is done by deleting the association first (set the owner field in Car to null and save the Car entity before removing the Person) or you could change the way the relationship is built by having a third table that handles it (when I had the same problem I opted for the first solution).

If you follow the first method, you should do the entire operation (setting the owner fields to null and removing the Car) inside a transaction, or you could end up in an inconsistent state if something goes wrong.

gscaparrotti
  • 663
  • 5
  • 21