Как смоделировать отношения один-к-одному с Spring Data JDBC?

Я хочу смоделировать отношения один-к-одному, используя Spring Data JDBC и PostgreSQL, но у меня возникают проблемы с правильной настройкой корневых агрегатов.

Есть следующий сценарий: рисунок, SQL
Каждый двигатель уникален, car имеет уникальный столбец engine_id который является внешним ключом engine.idТо же самое относится и к truck, Поэтому легковые и грузовые автомобили должны быть корневыми агрегатами, поэтому, когда легковой или грузовой автомобиль удаляется, также должна быть удалена указанная строка из таблицы двигателей.

Из моего понимания агрегатов JDBC Spring Data

Если несколько агрегатов ссылаются на один и тот же объект, этот объект не может быть частью этих агрегатов, ссылающихся на него, поскольку он может быть частью только одного агрегата.

Итак, вопросы:

  • Возможен ли обходной путь из-за объяснения выше, так что при выполнении операций CRUD над car а также truck изменения отражаются на engine также?
  • Каков наилучший способ реализации этих отношений в Java с использованием Spring Data JDBC?

Вот мой дубль, который не работает, но должен уточнить, что я пытаюсь достичь.

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

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

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. Если вам не нравятся два столбца, вы можете изменить отображение, чтобы использовать один и тот же столбец для обеих ссылок. Но тогда вы должны убедиться, что идентификаторы Car а также Vehicle различны. Даже тогда есть большая проблема с этим подходом:

    deleteAll либо на Car хранилище или Truck автомобиль удалит ВСЕ двигатели!!! Поэтому такой подход не рекомендуется!

    Если вы все еще хотите использовать его здесь, это код для схемы и сущностей.

    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 классы и таблицы. Один для Carс и один для Trucks.

  4. Если вы не хотите или не можете изменить схему базы данных, вы можете рассмотреть Engine, Car, а также Truck три отдельных агрегата. у тебя будет Long engineId в Car И в Truck, Затем можно выполнить каскадное удаление, используя прослушиватель событий дляAfterDeleteEvent,

Мне удалось найти решение, и проблема заключалась в том, что я не мог разобраться в ссылках Car а также Truck классы от Engine, когда в базе данных модель отличается.

Car.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

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

Однако это решение не соответствует требованию - операции CRUD выполняются на Engine.java отражаются на Car.java а также Truck.java, И я бы хотел, чтобы операции CRUD выполнялись на Car.java а также Truck.java отражаются на Engine.java,

Если у кого есть лучшее решение, напишите пожалуйста.

Другие вопросы по тегам