nagy.zsolt.hun
nagy.zsolt.hun

Reputation: 6694

Spring Data JPA - ManyToOne null

I'm learning Spring Data and JPA, and I have trouble making a simple ManyToOne - OneToMany connection within my entities.

Example: model relationship between cars and people (owners), one person can own multiple cars, but one car is owned by only one person.

Problem: owner of Car (annotated with ManyToOne) is always null.

App.java

@EnableJpaRepositories
@SpringBootApplication
public class App {

    @Autowired private PersonRepository personRepository;
    @Autowired private CarRepository carRepository;

    @EventListener
    @Transactional
    public void handleContextRefresh(ContextRefreshedEvent event) {
        Car car1 = new Car();
        Car car2 = new Car();
        Person person = new Person();

        car1.setName("Ferrari");
        car2.setName("Porsche");
        person.setName("Person Name");

        carRepository.save(car1);
        carRepository.save(car2);

        person.getCars().add(car1);
        person.getCars().add(car2);

        personRepository.save(person);

        carRepository.findAll().forEach(System.out::println);   // TODO owner should not be null
        personRepository.findAll().forEach(System.out::println);
    }

    public static void main(String[] args) throws IOException, URISyntaxException {
        SpringApplication.run(App.class, args);
    }

}

Car.java

@Entity
public class Car {

    @Id
    @GeneratedValue
    private UUID id;

    private String name;

    @ManyToOne
    private Person owner;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

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

    @Override
    public String toString() {
        return "Car [id=" + id + ", name=" + name + ", owner=" + owner + "]";
    }

}

CarRepository.java

@Repository
public interface CarRepository extends CrudRepository<Car, UUID>  { }

Person.java

@Entity
public class Person {

    @Id
    @GeneratedValue
    private UUID id;

    private String name;

    @OneToMany(mappedBy="owner", cascade=CascadeType.ALL)
    private Set<Car> cars = new HashSet<>();

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Set<Car> getCars() { return cars; }
    public void setCars(Set<Car> cars) { this.cars = cars; }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + ", cars=" + cars + "]";
    }

}

PersonRepository.java

@Repository
public interface PersonRepository extends CrudRepository<Person, UUID>  { }

Output:

Car [id=10b38a23-536d-4b8f-8578-16e12f2f65d1, name=Ferrari, owner=null]
Car [id=2c15abf9-cb96-4d70-9271-b25096764276, name=Porsche, owner=null]
Person [id=8484b16f-1645-49af-a4d5-4b6ba7238aba, name=Person Name, cars=[Car [id=2c15abf9-cb96-4d70-9271-b25096764276, name=Porsche, owner=null], Car [id=10b38a23-536d-4b8f-8578-16e12f2f65d1, name=Ferrari, owner=null]]]

Owner of both cars is null. How can I fix this?

Upvotes: 1

Views: 6417

Answers (1)

The problem is you are not setting the owner of the car via car.setOwner(...).

And also since you have cascade=CascadeType.ALL on the OneToMany annotation, you don't need to persist individual cars first and then the owner.

Just calling the personRepository.save(person); should be enough.

So with the above changes, you code :

            Car car1 = new Car();
            Car car2 = new Car();
            Person person = new Person();

            car1.setName("Ferrari");
            car2.setName("Porsche");
            person.setName("Person Name");
            person.getCars().add(car1);
            person.getCars().add(car2);
            car1.setOwner(person);
            car2.setOwner(person);

            personRepository.save(person);

And one more thing, you need to modify the toString method of Car object. Person object's toString is calling Car's toString which internally is calling Person toString again. This would lead to StackOverflowException. Consider not printing the entire Person object in the Car toString() method.

May be Car [id=" + id + ", name=" + name + ", owner=" + owner.getName() + "]

** UPDATE **

If you are looking for a specific solution where you want to ensure the Car owner is populated in DB without making a call to car.setOwner(...) method then you could update the OneToMany mapping as below:

    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="owner")
    private Set<Car> cars = new HashSet<>();

Basically, mappedBy is the one which decides who is the owner of the bidirectional relationship. In the first case, you have mentioned ownership of the relationship is owned by Car entity via mappedBy attribute. So unless we set the owner on the car object the ORM will not populate the owner column.

With the second approach of removing the mappedBy, the oneToMany becomes the owner of the relationship and so once you add to the car list of the Person, that person id will be used as the owner and will be populated in the DB against the owner column of the Car.

But, in the second case, and extra update is fired to update the owner column, apart ftom the normal insert, and so the owner column should be nullable.

Upvotes: 2

Related Questions