【问题标题】:How to model One-To-One relationship with Spring Data JDBC?如何使用 Spring Data JDBC 建模一对一关系?
【发布时间】:2019-05-05 18:49:30
【问题描述】:

我想使用 Spring Data JDBC 和 PostgreSQL 对一对一关系进行建模,但我无法以正确的方式设置根聚合。

有以下场景:PictureSQL
每个引擎都是唯一的,car 具有唯一列 engine_id,这是 engine.id 的外键,truck 也是如此。因此汽车和卡车应该是根聚合,所以当汽车或卡车被删除时,引擎表中的引用行也应该被删除。

根据我对Spring Data JDBC Aggregates的理解

如果多个聚合引用同一个实体,则该实体不能是引用它的那些聚合的一部分,因为它只能是一个聚合的一部分。

所以问题是:

  • 由于上面的解释,解决方法是否可行,以便通过对 cartruck 执行 CRUD 操作,更改也会反映到 engine 上?
  • 使用 Spring Data JDBC 在 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

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

卡车.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;
    }
}

【问题讨论】:

    标签: java postgresql spring-boot spring-data-jdbc


    【解决方案1】:

    设法找到了解决方案,但问题是我无法理解从 Engine 引用 CarTruck 类,当在数据库中模型不同时。

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

    卡车.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

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

    但是,此解决方案不满足要求 - 对 Engine.java 执行的 CRUD 操作会反映在 Car.javaTruck.java 上。我想实现在Car.javaTruck.java 上执行的CRUD 操作反映在Engine.java 上。

    如果有人有更好的解决方案,请发布。

    【讨论】:

      【解决方案2】:

      我看到了用 Java 建模的四个选项。 请注意,它们中的大多数都需要调整您的数据库架构。

      一般的问题是 Spring Data JDBC 假定被引用的实体 (Engine) 在其表中有一个列引用了拥有实体 (Car/Vehicle)。 这有一个问题:https://jira.spring.io/browse/DATAJDBC-128 从这里开始,您有以下选择:

      1. 将列添加到引擎表中,生成如下所示的实体和架构(所有实体都减少到与问题相关的最小值):

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

        我在 GitHub 上提供了一个完整的示例:https://github.com/schauder/so-sd-jdbc-multipleonetoone

      2. 如果您不喜欢这两列,您可以修改映射以将同一列用于两个引用。 但是你必须确保CarVehicle 的ID 是不同的。 即便如此,这种方法也存在一个大问题:

        Car 存储库或Truck 车辆上的deleteAll删除所有引擎!!! 因此不推荐这种方式!

        如果您仍然想使用它,这里是架构和实体的代码。

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

        完整示例在此提交中:https://github.com/schauder/so-sd-jdbc-multipleonetoone/tree/5570979ef85e30fe7a17a8ce48d867fdb79e212a

      3. 有两个独立的Engine 类和表。 一张给Cars,一张给Trucks。

      4. 如果您不想或无法更改数据库架构,您可以考虑使用 EngineCarTruck 三个单独的聚合。 你会在CarTruck 中有一个Long engineId。 然后可以使用event listener for AfterDeleteEvent 完成级联删除。

      【讨论】:

      • 感谢您提供的非常有用的解释,我真的很喜欢您使用 Spring Data JDBC btw 所做的事情。由于最初的问题有大约 10 个表引用 "engine" 表,因此 1) 和 3) 不在菜单中。我很可能会选择 4) 在数据库端执行级联删除而不是调用事件侦听器。在这种情况下,通过将@Transient 放在 Car 中的 Engine 字段上然后将自定义映射器添加到我编写的查询中来查询汽车时,将引擎加载到汽车中的最佳选择是?
      • 感谢您的友好反馈。是的,据我从简短的评论中可以看出,这种方法应该有效。如果您遇到问题,请打开一个新问题。
      • 第 4 种解决方案在某种情况下似乎是最好的,尽管在您处理现有模式时是一种解决方法。我对地址实体有类似的问题,被用户、公司和学校实体引用,将来可能会引入更多引用实体。继续创建更多的类和表来实现这一点并不理想......任何持久层都不应该真正决定 RDBMS 模式的结构......无论如何它都应该正常工作。
      • 单向一对一映射比较重要,大部分schema都遵循这个,我觉得这个必须先解决。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-07
      • 1970-01-01
      • 2021-10-17
      • 2021-12-03
      • 1970-01-01
      相关资源
      最近更新 更多