如何建立基于非关键字段的关系?
How to have a relationship based on a non-key field?
我有以下两个实体,当我尝试向我的汽车添加物品时 table 它显示以下错误消息;因此,它不允许我拥有一辆以上 'Auto' 传输.
错误:
#1062 - Duplicate entry 'Auto' for key 'UK_bca5dfkfd4fjdhfh4ddirfhdhesr'
实体:
汽车
@Entity
public class Car implements java.io.Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
...
}
汽车示例值 table:
10 Auto
12 Auto
43 Manual
54 Manual
65 Normal
68 Standard
90 Normal
99 NoGear
汽车工厂
@Entity
public class CarFactory implements java.io.Serializable {
@Id
@JoinColumn(name="transmission",referencedColumnName = "transmission")
@ManyToOne
Car car;
@Id
@JoinColumn(name="factory_id", referencedColumnName= "id")
@ManyToOne
Factory factory;
...
}
CarFactory 的预期值 table
Auto Fac1
Auto Fac2
Manual Fac1
Auto Fac5
Standard Fac6
Normal Fac3
NoGear Fac1
我也遵循了这个 question 的答案,但它没有用。
长话短说,我需要一个 table,其中包含来自其他 table 的两个外键,以及组合主键。它不应该强制唯一的外键参与 tables.
我模拟了你的用例,你可以在 GitHub 上找到测试。
这些是映射:
@Entity(name = "Car")
public static class Car implements Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
}
@Entity(name = "Factory")
public static class Factory implements Serializable {
@Id
@GeneratedValue
long id;
}
@Entity(name = "CarFactory")
public static class CarFactory implements Serializable {
@Id
@ManyToOne
@JoinColumn(name = "transmission", referencedColumnName = "transmission")
Car car;
@ManyToOne
@Id
Factory factory;
public void setCar(Car car) {
this.car = car;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
这是您添加一些测试数据的方式:
doInTransaction(session -> {
Car car = new Car();
car.transmission = "Auto";
Car car1 = new Car();
car1.transmission = "Manual";
Factory factory = new Factory();
session.persist(factory);
session.persist(car);
session.persist(car1);
CarFactory carFactory = new CarFactory();
carFactory.setCar(car);
carFactory.setFactory(factory);
CarFactory carFactory1 = new CarFactory();
carFactory1.setCar(car1);
carFactory1.setFactory(factory);
session.persist(carFactory);
session.persist(carFactory1);
});
测试工作正常:
@Test
public void test() {
doInTransaction(session -> {
List<CarFactory> carFactoryList = session.createQuery("from CarFactory").list();
assertEquals(2, carFactoryList.size());
});
}
更新
由于以下唯一约束,您遇到异常:
alter table Car add constraint UK_iufgc8so6uw3pnyih5s6lawiv unique (transmission)
这是正常行为,因为 FK 必须唯一标识 PK 行。就像您不能有更多行具有相同的主键一样,您不能有一个 FK 标识符引用超过一行。
你的映射有问题。您需要引用其他内容,而不是 transmision
。您需要一个唯一的汽车标识符,例如 VIN(车辆识别码),因此您的映射变为:
@Entity(name = "Car")
public static class Car implements Serializable {
@Id
@GeneratedValue
long id;
@Column(name="vin", nullable = false)
String vin;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
}
@Entity(name = "CarFactory")
public static class CarFactory implements Serializable {
@Id
@ManyToOne
@JoinColumn(name = "vin", referencedColumnName = "vin")
Car car;
@ManyToOne
@Id
Factory factory;
public void setCar(Car car) {
this.car = car;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
这样,vin
是唯一的,子关联可以引用 且仅 一个父。
你为什么不使用 @ManyToMany
关系?
@Entity
public class Car implements java.io.Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@ManyToMany
@JoinTable(
name="CARFACTORY",
joinColumns={@JoinColumn(name="transmission", referencedColumnName="transmission")},
inverseJoinColumns={@JoinColumn(name="factory_id", referencedColumnName="id")})
Set<Factory> factories;
...
}
...没有测试代码,但它应该可以工作。
您的 CarFactory
中有一个 ManyToOne
关系引用 Car 中的传输字段。这意味着 Car
中的传输字段必须是唯一的。
它看起来就像您试图向您的汽车添加多个具有相同传输值的项目 table,但是您的设计表明您只需要在 Car
table 中添加一个条目每次传输,您只需要为每次传输添加多个 CarFactory
条目。
这里的问题是您使用非主键字段作为外键,这似乎不正确,您的 transmission
字段应该是唯一的,这一行不正确:
@JoinColumn(name="transmission",referencedColumnName = "transmission")
你这里有一个 Many-To-Many
映射,它需要在关联 table 中有一个 @EmbeddedId property,你的代码应该是这样的:
汽车工厂class
@Entity
public class CarFactory {
private CarFactoryId carFactoryId = new CarFactoryId();
@EmbeddedId
public CarFactoryId getCarFactoryId() {
return carFactoryId;
}
public void setCarFactoryId(CarFactoryId carFactoryId) {
this.carFactoryId = carFactoryId;
}
Car car;
Factory factory;
//getters and setters for car and factory
}
CarFactoryId class
@Embeddable
public class CarFactoryId implements Serializable{
private static final long serialVersionUID = -7261887879839337877L;
private Car car;
private Factory factory;
@ManyToOne
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@ManyToOne
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
public CarFactoryId(Car car, Factory factory) {
this.car = car;
this.factory = factory;
}
public CarFactoryId() {}
}
汽车class
@Entity
public class Car {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
private Set<CarFactory> carFactories = new HashSet<CarFactory>();
@OneToMany(mappedBy = "primaryKey.car",
cascade = CascadeType.ALL)
public Set<CarFactory> getCarFactories() {
return carFactories;
}
...
}
对于Factory
class也是一样,注意有几种方法可以定义一个embedded id
或者一个composite id
,看看:
- Mapping ManyToMany with composite Primary key and Annotation.
- How to create hibernate composite key using annotations
注:
在我的示例中,我没有在复合 id 中使用 transmission
字段,但您可以使用它,您可以参见下面的示例:
@JoinColumn(name="transmission",referencedColumnName = "transmission")
以此改变
@JoinColumn(name="car_id",referencedColumnName = "id")
你的问题有解释的余地:一个车型可能需要一组工厂,它是在不同的工厂生产的。或者汽车实例的不同部分是在不同的工厂制造的。
对于您的示例和预期数据,没有解决方案。如果不同的工厂可以 produce/have 汽车配备相同类型的变速器,则无法确定生产的汽车 in/is 与正确的工厂相关联。你只能说,它是具有相同变速器的工厂之一。但是,这不受任何映射支持。
如果要关联右边factory/factories,则需要在Cartable中添加更多关于CarFactory的信息(例如Factory)。这取决于您的要求,但我想,chsdk 的答案接近他们。
我有以下两个实体,当我尝试向我的汽车添加物品时 table 它显示以下错误消息;因此,它不允许我拥有一辆以上 'Auto' 传输.
错误:
#1062 - Duplicate entry 'Auto' for key 'UK_bca5dfkfd4fjdhfh4ddirfhdhesr'
实体:
汽车
@Entity
public class Car implements java.io.Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
...
}
汽车示例值 table:
10 Auto
12 Auto
43 Manual
54 Manual
65 Normal
68 Standard
90 Normal
99 NoGear
汽车工厂
@Entity
public class CarFactory implements java.io.Serializable {
@Id
@JoinColumn(name="transmission",referencedColumnName = "transmission")
@ManyToOne
Car car;
@Id
@JoinColumn(name="factory_id", referencedColumnName= "id")
@ManyToOne
Factory factory;
...
}
CarFactory 的预期值 table
Auto Fac1
Auto Fac2
Manual Fac1
Auto Fac5
Standard Fac6
Normal Fac3
NoGear Fac1
我也遵循了这个 question 的答案,但它没有用。
长话短说,我需要一个 table,其中包含来自其他 table 的两个外键,以及组合主键。它不应该强制唯一的外键参与 tables.
我模拟了你的用例,你可以在 GitHub 上找到测试。
这些是映射:
@Entity(name = "Car")
public static class Car implements Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
}
@Entity(name = "Factory")
public static class Factory implements Serializable {
@Id
@GeneratedValue
long id;
}
@Entity(name = "CarFactory")
public static class CarFactory implements Serializable {
@Id
@ManyToOne
@JoinColumn(name = "transmission", referencedColumnName = "transmission")
Car car;
@ManyToOne
@Id
Factory factory;
public void setCar(Car car) {
this.car = car;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
这是您添加一些测试数据的方式:
doInTransaction(session -> {
Car car = new Car();
car.transmission = "Auto";
Car car1 = new Car();
car1.transmission = "Manual";
Factory factory = new Factory();
session.persist(factory);
session.persist(car);
session.persist(car1);
CarFactory carFactory = new CarFactory();
carFactory.setCar(car);
carFactory.setFactory(factory);
CarFactory carFactory1 = new CarFactory();
carFactory1.setCar(car1);
carFactory1.setFactory(factory);
session.persist(carFactory);
session.persist(carFactory1);
});
测试工作正常:
@Test
public void test() {
doInTransaction(session -> {
List<CarFactory> carFactoryList = session.createQuery("from CarFactory").list();
assertEquals(2, carFactoryList.size());
});
}
更新
由于以下唯一约束,您遇到异常:
alter table Car add constraint UK_iufgc8so6uw3pnyih5s6lawiv unique (transmission)
这是正常行为,因为 FK 必须唯一标识 PK 行。就像您不能有更多行具有相同的主键一样,您不能有一个 FK 标识符引用超过一行。
你的映射有问题。您需要引用其他内容,而不是 transmision
。您需要一个唯一的汽车标识符,例如 VIN(车辆识别码),因此您的映射变为:
@Entity(name = "Car")
public static class Car implements Serializable {
@Id
@GeneratedValue
long id;
@Column(name="vin", nullable = false)
String vin;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
Set<CarFactory> factories;
}
@Entity(name = "CarFactory")
public static class CarFactory implements Serializable {
@Id
@ManyToOne
@JoinColumn(name = "vin", referencedColumnName = "vin")
Car car;
@ManyToOne
@Id
Factory factory;
public void setCar(Car car) {
this.car = car;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
这样,vin
是唯一的,子关联可以引用 且仅 一个父。
你为什么不使用 @ManyToMany
关系?
@Entity
public class Car implements java.io.Serializable {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
@ManyToMany
@JoinTable(
name="CARFACTORY",
joinColumns={@JoinColumn(name="transmission", referencedColumnName="transmission")},
inverseJoinColumns={@JoinColumn(name="factory_id", referencedColumnName="id")})
Set<Factory> factories;
...
}
...没有测试代码,但它应该可以工作。
您的 CarFactory
中有一个 ManyToOne
关系引用 Car 中的传输字段。这意味着 Car
中的传输字段必须是唯一的。
它看起来就像您试图向您的汽车添加多个具有相同传输值的项目 table,但是您的设计表明您只需要在 Car
table 中添加一个条目每次传输,您只需要为每次传输添加多个 CarFactory
条目。
这里的问题是您使用非主键字段作为外键,这似乎不正确,您的 transmission
字段应该是唯一的,这一行不正确:
@JoinColumn(name="transmission",referencedColumnName = "transmission")
你这里有一个 Many-To-Many
映射,它需要在关联 table 中有一个 @EmbeddedId property,你的代码应该是这样的:
汽车工厂class
@Entity
public class CarFactory {
private CarFactoryId carFactoryId = new CarFactoryId();
@EmbeddedId
public CarFactoryId getCarFactoryId() {
return carFactoryId;
}
public void setCarFactoryId(CarFactoryId carFactoryId) {
this.carFactoryId = carFactoryId;
}
Car car;
Factory factory;
//getters and setters for car and factory
}
CarFactoryId class
@Embeddable
public class CarFactoryId implements Serializable{
private static final long serialVersionUID = -7261887879839337877L;
private Car car;
private Factory factory;
@ManyToOne
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@ManyToOne
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
public CarFactoryId(Car car, Factory factory) {
this.car = car;
this.factory = factory;
}
public CarFactoryId() {}
}
汽车class
@Entity
public class Car {
@Id
@GeneratedValue
long id;
@Column(name="transmission", nullable = false)
String transmission;
private Set<CarFactory> carFactories = new HashSet<CarFactory>();
@OneToMany(mappedBy = "primaryKey.car",
cascade = CascadeType.ALL)
public Set<CarFactory> getCarFactories() {
return carFactories;
}
...
}
对于Factory
class也是一样,注意有几种方法可以定义一个embedded id
或者一个composite id
,看看:
- Mapping ManyToMany with composite Primary key and Annotation.
- How to create hibernate composite key using annotations
注:
在我的示例中,我没有在复合 id 中使用 transmission
字段,但您可以使用它,您可以参见下面的示例:
@JoinColumn(name="transmission",referencedColumnName = "transmission")
以此改变
@JoinColumn(name="car_id",referencedColumnName = "id")
你的问题有解释的余地:一个车型可能需要一组工厂,它是在不同的工厂生产的。或者汽车实例的不同部分是在不同的工厂制造的。 对于您的示例和预期数据,没有解决方案。如果不同的工厂可以 produce/have 汽车配备相同类型的变速器,则无法确定生产的汽车 in/is 与正确的工厂相关联。你只能说,它是具有相同变速器的工厂之一。但是,这不受任何映射支持。 如果要关联右边factory/factories,则需要在Cartable中添加更多关于CarFactory的信息(例如Factory)。这取决于您的要求,但我想,chsdk 的答案接近他们。