【问题标题】:Spring Data JPA - ManyToOne nullSpring Data JPA - ManyToOne null
【发布时间】:2018-09-04 01:09:02
【问题描述】:

我正在学习 Spring Data 和 JPA,但无法在我的实体中建立简单的 ManyToOne - OneToMany 连接。

示例:汽车与人(车主)的模型关系,一个人可以拥有多辆汽车,但一辆车只属于一个人。

问题:Car 的所有者(使用 ManyToOne 注释)始终为空。

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);
    }

}

汽车.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>  { }

输出:

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]]]

两辆车的车主都为空。我该如何解决这个问题?

【问题讨论】:

  • 这都是在单个事务的上下文中,不同的存储库处理保存,并且没有刷新,所以我不确定您是否可以从 carRepo 检索 personRepo 所做的更改。简单的答案是只打电话给car.setOwner 并把人传进去。否则你需要使用flush,使用entityManager 实例而不是两个单独的repos,使用personRepo 来取回汽车,或重组你的代码以用不同的方法处理事情。

标签: spring jpa spring-data


【解决方案1】:

问题是你是not setting the owner of the car via car.setOwner(...)

而且由于您在 OneToMany 注释上有 cascade=CascadeType.ALL,因此您无需先保留个别汽车,然后再保留所有者。

只需拨打personRepository.save(person); 就足够了。

因此,通过上述更改,您可以编写代码:

            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);

还有一件事,你需要修改Car对象的toString方法。 Person 对象的 toString 正在调用 Car 的 toString,它在内部再次调用 Person toString。这将导致StackOverflowException。考虑不要在Car toString() 方法中打印整个Person object

可能是Car [id=" + id + ", name=" + name + ", owner=" + owner.getName() + "]

** 更新 **

如果您正在寻找一个特定的解决方案,希望在不调用car.setOwner(...) 方法的情况下确保将车主填充到数据库中,那么您可以更新OneToMany 映射,如下所示:

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

基本上,mappedBy 决定谁是双向关系的所有者。在第一种情况下,您提到关系的所有权由Car 实体通过mappedBy 属性拥有。因此,除非我们在汽车对象上设置所有者,否则 ORM 将不会填充 owner 列。

使用删除 mappedBy 的第二种方法,oneToMany 成为关系的所有者,因此一旦您添加到 Person 的汽车列表,该人员 id 将用作所有者,并将填充到对 Car 的所有者列进行 DB。

但是,在第二种情况下,除了正常的插入之外,还会触发额外的更新来更新所有者列,因此所有者列应该可以为空。

【讨论】:

  • 据我了解,这是少数可能的解决方案之一。如果没有更详细的答案,我会接受。感谢您指出由于 CascadeType.ALL 以及潜在的 StackOverFlowException,因此不需要保存。
  • 不确定您是否正在寻找一个特定的解决方案,以确保车主在 DB 中填充而不调用 car.setOwner(...) 方法?如果是这样,您可以查看更新后的答案。
  • 如果我采用第一种方法,(Car 拥有关系),为什么 person.getCars().add(car1) 调用也是必要的?
  • 因为 cascadeType.ALL 在 OneToMany 字段上。由于我们希望坚持 Person 以传播到 Set 中的元素,我们需要填充它。否则,您需要先对 Person 进行持久化,然后在个别汽车上调用 persist。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-24
  • 1970-01-01
  • 2022-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-05
相关资源
最近更新 更多