【问题标题】:How to have a relationship based on a non-key field?如何建立基于非关键字段的关系?
【发布时间】:2015-07-11 23:32:54
【问题描述】:

我有以下两个实体,当我尝试将项目添加到我的汽车表时,它显示以下错误消息;因此,它不允许我拥有多于一辆具有“自动”传输的汽车。

错误:

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

汽车表的示例值

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 表的预期值

Auto Fac1
Auto Fac2
Manual Fac1
Auto Fac5
Standard Fac6
Normal Fac3
NoGear Fac1

我也关注了这个question 的答案,但它没有用。

长话短说,我需要一个表,其中包含来自其他表的两个外键,以及组合的主键。它不应该强制参与表中的唯一外键。

【问题讨论】:

  • 这不是多对一的关联,因为多辆汽车有相同的传输。您可能应该在工厂中有一个 Set supportedTransmissions,并使用查询来查找支持与给定汽车相同的传输的所有工厂。
  • @JBNizet 那么哪个字段将是表的主键?我认为有一个带有复合键的表是很常见的。这样,主键都不是唯一的,但它们的组合是唯一的
  • 当我尝试将项目添加到我的 car 表时,它会显示以下错误消息 - 您真的需要将 Auto multiletimes 添加到您的汽车表吗?在我看来,您只需要在您的汽车表中有 1 个带有传输“自动”的条目,并且在您的 CarFactory 表中有多个这样的条目。
  • 也检查这个问题:stackoverflow.com/questions/7594812 它非常相似,并使用 Hibernate 的@JoinFormula 解决。
  • 你为什么要违反规则?你到底想要什么?? @JoinColumn(name="transmission",referencedColumnName = "transmission") 你为什么不用@JoinColumn(name="car_id",referencedColumnName = "id") 改变这个???

标签: java hibernate jpa orm hibernate-mapping


【解决方案1】:

我模拟了您的用例,您可以在 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 行。就像您不能有更多具有相同 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 是唯一的,并且子关联可以引用唯一一个父级。

【讨论】:

  • 我对你的解释有点困惑,假设我有 Auto 和 Fac1,它们的组合将是唯一的,所以我不会有任何其他这样的记录;但是,我可能有 Manual Fac1 或 Normal Fac1。那么为什么它可能会显示不同的结果呢?请您进一步详细说明。
  • 这是一个只有两个字段的关联表,两个字段都可以有非唯一值,但它们的组合必须是唯一的。
  • 谢谢,这是否意味着应该从 CarFactory 的工厂字段中删除 @JoinColumn 并且该类应该是静态的?你能进一步解释一下吗?另外你对 chsdk 的回答有什么想法?
  • 这个类是静态的,因为我把它嵌入了一个测试类,但那不是你的情况。
  • 谢谢,但它仍然显示相同的错误消息。我不能拥有两辆具有相同变速器的汽车。假设汽车 id 1 自动,汽车 id 2 自动
【解决方案2】:

这里的问题是您使用非主键字段作为外键,这似乎是不正确的,而您的 transmission 字段应该是唯一的,这一行不正确:

@JoinColumn(name="transmission",referencedColumnName = "transmission")

您这里有一个Many-To-Many 映射,需要在关联表中添加一个@EmbeddedId property,您的代码应该是这样的:

汽车工厂类

@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 类

@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() {}

}

汽车类

@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 类也是如此,注意有几种方法可以定义 embedded idcomposite id,看看:

注意:

在我的例子中我没有在复合id中使用transmission字段但是你可以使用它,你可以看下面的例子:

【讨论】:

  • 谢谢,您对 Vlad Mihalcea 的回答有何看法?
  • 这也是一种很好的方法,似乎最适合您的情况。
  • +1 对于多字段主键(复合键实体或联接表),我建议使用 @EmbeddedId。我们有 130 个带有多列键的表,它们都使用@EmbeddedId。我们从来没有遇到过任何问题。
  • @DuncanKinnear 你对弗拉德的回答有何看法?
  • @chsdk 我的主要问题是传输,因为将其作为外键使其独一无二。如何在关系表中拥有它而不使其在父表中唯一?
【解决方案3】:

你为什么不使用@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;
    ...
}

...没有测试代码,但它应该可以工作。

【讨论】:

  • Hibernate 社区不鼓励多对多关系。
  • @DanielNewtown 谁是“Hibernate 社区”?你在哪里读到的?你能提供任何链接吗(你让我很好奇)?
【解决方案4】:

在您的CarFactory 中有一个ManyToOne 关系引用Car 中的传输字段。这意味着Car 中的传输字段必须是唯一的。

看起来您正在尝试将具有相同传输值的多个项目添加到 Car 表中,但是您的设计建议您每次传输只需要在 Car 表中输入一个条目,并且您只需要添加多个 @987654325 @ 每次传输的条目。

【讨论】:

  • 您能否发布您填充数据库的代码部分(创建 Car 和 CarFactory 实例)?
【解决方案5】:
@JoinColumn(name="transmission",referencedColumnName = "transmission") 

用这个改变

@JoinColumn(name="car_id",referencedColumnName = "id")

【讨论】:

    【解决方案6】:

    您的问题留有解释的余地​​:一个汽车模型可能需要一组工厂,该模型是在不同的工厂生产的。或者汽车实例的不同部分是在不同的工厂建造的。 对于您的样本和预期数据,没有解决方案。如果不同的工厂可以生产/拥有具有相同类型变速器的汽车,则无法确定哪辆汽车是在正确的工厂生产/与正确的工厂相关联的。只能说,是同款变速箱的工厂之一。但是,任何映射都不支持这一点。 如果要关联正确的工厂/工厂,则需要将有关 CarFactory 的更多信息添加到 Car 表(例如 Factory)。这取决于您的要求,但我想,chsdk 的答案接近他们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-04
      • 1970-01-01
      相关资源
      最近更新 更多