如何建立基于非关键字段的关系?

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 的答案接近他们。