Как смоделировать отношения один-к-одному с 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 Начиная с этого у вас есть следующие варианты:
добавьте столбцы в таблицу механизма, что приведет к появлению сущностей и схемы, как показано ниже (все сущности уменьшены до минимума, соответствующего проблеме):
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.
Если вам не нравятся два столбца, вы можете изменить отображение, чтобы использовать один и тот же столбец для обеих ссылок. Но тогда вы должны убедиться, что идентификаторы
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.
Есть два отдельных
Engine
классы и таблицы. Один дляCar
с и один дляTruck
s.Если вы не хотите или не можете изменить схему базы данных, вы можете рассмотреть
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
,
Если у кого есть лучшее решение, напишите пожалуйста.