简体   繁体   English

如何使用Spring Data JDBC建模一对一关系?

[英]How to model One-To-One relationship with Spring Data JDBC?

I want to model One-To-One relationship using Spring Data JDBC and PostgreSQL but I'm having trouble setting up root aggregates the right way. 我想使用Spring Data JDBC和PostgreSQL建立一对一关系模型,但是我无法以正确的方式设置根聚合。

There's following scenario: Picture , SQL 有以下方案: 图片SQL
Each engine is unique, car has unique column engine_id which is foreign key of engine.id , same goes for truck . 每个引擎是独一无二的, car具有独特的列engine_id这是外键engine.id ,这同样适用于truck Therefore car and truck should be root aggregates so when car or truck is deleted, referenced row from engine table should be deleted as well. 因此,汽车和卡车应该是根集合,因此,当汽车或卡车被删除时,引擎表中的引用行也应被删除。

From my understanding of Spring Data JDBC Aggregates 根据我对Spring Data JDBC聚合的理解

If multiple aggregates reference the same entity, that entity can't be part of those aggregates referencing it since it only can be part of exactly one aggregate. 如果多个集合引用同一个实体,则该实体不能成为引用该实体的那些集合的一部分,因为它只能是一个集合的一部分。

So the questions are: 所以问题是:

  • Is workaround possible due to explanation above so that by performing CRUD operations on car and truck changes are reflected to engine as well ? 是否可以通过上述解释解决问题,以便通过对cartruck执行CRUD操作,将更改也反映到engine
  • What's the best way to implement this relationship in java using Spring Data JDBC ? 使用Spring Data JDBC在Java中实现这种关系的最佳方法是什么?

Here's my take which does not work but should clarify what I'm trying to accomplish. 这是我的工作,它不起作用,但应阐明我要完成的工作。

Car.java 汽车.java

package com.example.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import java.util.UUID;

@Table("car")
public class Car implements Persistable<UUID> {

    @Id
    private UUID id;

    String brand;

    String model;

    @Column("engine_id")
    Engine engine;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }
}

Engine.java Engine.java

package com.example.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;
import java.util.UUID;

@Table("engine")
public class Engine implements Persistable<UUID> {

    @Id
    private UUID id;

    String name;

    LocalDateTime dateCreated;

    String type;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }
}

Truck.java 卡车.java

package com.example.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import java.util.UUID;

@Table("truck")
public class Truck implements Persistable<UUID> {

    @Id
    private UUID id;

    String brand;

    String model;

    Integer cargoMaxWeight;

    String truckType;

    @Column("engine_id")
    Engine engine;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }
}

Managed to find the solution and the problem was that I couldn't get my head around referencing Car and Truck classes from Engine , when in database the model is different. 设法找到解决方案,问题是当数据库中的模型不同时,我无法TruckEngine引用CarTruck类。

Car.java 汽车.java

package com.backend.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;

import java.util.Objects;
import java.util.UUID;

public class Car implements Persistable<UUID> {

    @Id
    private UUID id;

    private String brand;

    private String model;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Car)) {
            return false;
        }
        Car car = (Car) o;
        return Objects.equals(id, car.id) &&
            Objects.equals(brand, car.brand) &&
            Objects.equals(model, car.model);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, brand, model);
    }
}

Truck.java 卡车.java

package com.backend.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;

import java.util.Objects;
import java.util.UUID;

@Table("truck")
public class Truck implements Persistable<UUID> {

    @Id
    private UUID id;

    private String brand;

    private String model;

    private Integer cargoMaxWeight;

    private String truckType;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public Integer getCargoMaxWeight() {
        return cargoMaxWeight;
    }

    public void setCargoMaxWeight(Integer cargoMaxWeight) {
        this.cargoMaxWeight = cargoMaxWeight;
    }

    public String getTruckType() {
        return truckType;
    }

    public void setTruckType(String truckType) {
        this.truckType = truckType;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Truck)) {
            return false;
        }
        Truck truck = (Truck) o;
        return Objects.equals(id, truck.id) &&
            Objects.equals(brand, truck.brand) &&
            Objects.equals(model, truck.model) &&
            Objects.equals(cargoMaxWeight, truck.cargoMaxWeight) &&
            Objects.equals(truckType, truck.truckType);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, brand, model, cargoMaxWeight, truckType);
    }
}

Engine.java Engine.java

package com.backend.dao.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;

@Table("engine")
public class Engine implements Persistable<UUID> {

    @Id
    private UUID id;

    private String name;

    private LocalDateTime dateCreated;

    private String type;

    @Column("engine_id")
    private Car car;

    @Column("engine_id")
    private Truck truck;

    public void setId(UUID id) {
        this.id = id;
    }

    @Override
    public UUID getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

    public LocalDateTime getDateCreated() {
        return dateCreated;
    }

    public void setDateCreated(LocalDateTime dateCreated) {
        this.dateCreated = dateCreated;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public boolean isNew() {
        return id == null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Engine)) {
            return false;
        }
        Engine engine = (Engine) o;
        return Objects.equals(id, engine.id) &&
            Objects.equals(name, engine.name) &&
            Objects.equals(dateCreated, engine.dateCreated) &&
            Objects.equals(type, engine.type);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, name, dateCreated, type);
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Truck getTruck() {
        return truck;
    }

    public void setTruck(Truck truck) {
        this.truck = truck;
    }
}

However this solution does not meet the requirement - CRUD operations performed on Engine.java are reflected on Car.java and Truck.java . 但是,此解决方案不能满足要求-在Engine.java上执行的CRUD操作反映在Car.javaTruck.java And I'd want to achieve that CRUD operations performed on Car.java and Truck.java are reflected on Engine.java . 我想实现在Car.javaTruck.java上执行的CRUD操作反映在Engine.java

If anyone has a better solution, post please. 如果有人有更好的解决方案,请发布。

I see four options to model this in Java. 我看到了四个用Java建模的选项。 Note that most of them require tweaking your database schema. 请注意,其中大多数都需要调整数据库架构。

The general problem is that Spring Data JDBC assumes that the referenced entity ( Engine ) has a column in its table that references the owning entity ( Car / Vehicle ). 普遍的问题是,Spring Data JDBC假定引用的实体( Engine )在其表中具有引用拥有的实体( Car / Vehicle )的列。 There is an issue for this: https://jira.spring.io/browse/DATAJDBC-128 Starting from this you have the following options: 对此存在一个问题: https: //jira.spring.io/browse/DATAJDBC-128从此开始,您有以下选择:

  1. add to columns to the engine table resulting in entities and schema like the following (all entities are reduced to their minimum relevant to the problem): 添加到引擎表的列中,得到如下所示的实体和架构(所有实体均减少到与问题相关的最低限度):

     public class Car { @Id Long id; String name; Engine engine; } public class Truck { @Id Long id; String name; Engine engine; } public class Engine { String name; } CREATE TABLE CAR ( id BIGINT IDENTITY, NAME VARCHAR(200) ); CREATE TABLE TRUCK ( ID BIGINT IDENTITY, NAME VARCHAR(200) ); CREATE TABLE ENGINE ( TRUCK BIGINT, CAR BIGINT, NAME VARCHAR(200), FOREIGN KEY (TRUCK) REFERENCES TRUCK (ID), FOREIGN KEY (CAR) REFERENCES CAR (ID) ); 

    I provided a complete example on GitHub: https://github.com/schauder/so-sd-jdbc-multipleonetoone . 我在GitHub上提供了完整的示例: https : //github.com/schauder/so-sd-jdbc-multipleonetoone

  2. If you don't like the two columns you can modify the mapping to use the same column for both references. 如果您不喜欢这两列,则可以修改映射以将相同的列用于两个引用。 But then you have to make sure that the ids of Car and Vehicle are distinct. 但是随后您必须确保CarVehicle的ID是不同的。 Even then there is a big problem with this approach: 即使这样,这种方法仍然存在很大的问题:

    deleteAll on either the Car repository or the Truck vehicle will delete ALL the engines!!! Car资料库或Truck车辆上的deleteAll删除所有引擎!!! Therefore this approach is not recommended! 因此,不建议使用此方法!

    If you still want to use it here is the code for schema and entities. 如果仍要使用它,则这里是架构和实体的代码。

     public class Car { @Id Long id; String name; @Column(value = "vehicle") Engine engine; } public class Truck { @Id Long id; String name; @Column(value = "vehicle") Engine engine; } public class Engine { String name; } CREATE TABLE CAR ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY , NAME VARCHAR(200) ); CREATE TABLE TRUCK ( ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH -1, INCREMENT BY -1) PRIMARY KEY , NAME VARCHAR(200) ); CREATE TABLE ENGINE ( VEHICLE BIGINT, NAME VARCHAR(200), ); 

    And the complete example is on this commit: https://github.com/schauder/so-sd-jdbc-multipleonetoone/tree/5570979ef85e30fe7a17a8ce48d867fdb79e212a . 有关此提交的完整示例,请参见: https : //github.com/schauder/so-sd-jdbc-multipleonetoone/tree/5570979ef85e30fe7a17a8ce48d867fdb79e212a

  3. Have two separate Engine classes and tables. 有两个单独的Engine类和表。 One for Car s and one for Truck s. 一辆用于Car ,一辆用于Truck

  4. If you don't want or can't change your database schema you can consider Engine , Car , and Truck three separate aggregates. 如果您不希望或无法更改数据库架构,则可以考虑EngineCarTruck三个独立的聚合。 you would have a Long engineId in Car and in Truck . 您将在CarTruck拥有一个Long engineId The cascading delete could then be done using an event listener for AfterDeleteEvent . 然后可以使用AfterDeleteEvent事件侦听器完成级联删除。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM