【问题标题】:Hibernate composite foreign keys share columnHibernate 复合外键共享列
【发布时间】:2014-02-26 00:28:16
【问题描述】:

我正在处理无法更改的旧数据库。该数据库定期与创建新条目的辅助实例同步。为了使实体的主键对所有实例都是唯一的,所有实体都具有复合主键,其中包括由其原始数据库生成的代理键和标识原始实例的服务器 ID。

复合主键对于 Hibernate / JPA 来说是没有问题的,它看起来像这样:

@Embeddable
public class ID implements Serializable {
    private Long autoin;      // Surrogate key
    private Integer serverId; // instance identifier

    @Column(name = "autoin_fix")
    public Long getAutoin() {
        return this.autoin;
    }
    @Column(name = "servdat_fk")
    public Integer getServerId() {
        return this.serverId;
    }

    // ... setter, equals, hashCode ...
}

现在考虑以下实体:

@Entity
@Table(name = "LEADS")
public class Request {
    private Long id;

    private Article article;
    private Location location;

    @Id
    @Column(name = "autoin_fix")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "auto-increment")
    @GenericGenerator(name = "auto-increment", strategy = "sequence",
            parameters = @org.hibernate.annotations.Parameter(name = "sequence", value = "AUTOINCREMENT"))
    public Long getId() {
        return this.id;
    }

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix", 
                    insertable = false, updatable = false), // Here is the problem!
        @JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk", 
                    insertable = false, updatable = false)
    })
    public Article getArticle() {
        return this.article;
    }

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name = "standort_fk", referencedColumnName = "autoin_fix"),
        @JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk")
    })
    public Location getLocation() {
        return this.location;
    }

    // Setters omitted
}

Request 实体引用了另外两个实体,它们都使用复合主键来标识自己。但由于从业务逻辑的角度来看,这两个实体(文章和位置)的 serverId总是必须相同,因此数据库中的 serverId 只有一列:

TABLE leads
-------------
autoin_fix
artikel_fk
standort_fk
servdat_fk  // This only exists once, but is part of the association to both Article and Location!

为了让应用程序完全启动,我必须将 insertable = false, updatable = false 添加到 Article 关联中,这不是我想要的。如果我尝试持久化 Request 对象,则不会写入字段“artikel_fk”,在数据库中留下空值,因为我告诉 hibernate 它是只读的。并且只允许在其中一个连接列上使用 insertable = false, updatable = false。

// This is illegal and the application won't start
@JoinColumns({
        @JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix"),
        @JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk", insertable = false, updatable = false)
    })

有人知道这个问题的解决方案吗?或者这不可能使用 Hibernate / JPA 进行映射?

编辑:文章和位置的(简化)定义(真的没那么有趣,重要的是它们使用复合主键):

@Entity
@Table(name = "ARTIKEL")
public class Article {
    private ID id;
    private String headline;
    private String description;

    @EmbeddedId
    public ID getId() {
        return this.id;
    }

    @Column(name = "artbesch")
    public String getDescription() {
        return this.description;
    }

    @Column(name = "artueschr")
    public String getHeadline() {
        return this.headline;
    }

    // Setters omitted
}

@Entity
@Table(name = "STANDORT")
public class Location {
    private ID id;
    private GeoCoordinates geoCoordinates;
    private Name name;

    @EmbeddedId
    public ID getId() {
        return this.id;
    }

    @Embedded
    public GeoCoordinates getGeoCoordinates() {
        return this.geoCoordinates;
    }

    @Embedded
    public Name getName() {
        return this.name;
    }

    // Setters omitted
}

【问题讨论】:

  • 您能按照我们的方式发布文章和位置的定义吗?

标签: java hibernate jpa orm jpa-2.0


【解决方案1】:

我认为如果一个依赖实体的派生标识符是一个嵌入的id类的形式,那么那个id类的每个代表关系的属性都应该被对应关系属性上的@MapsId注解引用。

例如:

@Entity
public class Request {
    // ...
    @Id
    int id;

    @MapsId
    @ManyToOne
    private Article article;
    // ...
}

我希望这有效,因为我现在无法尝试。

【讨论】:

  • 我明天试试,但看起来很有希望。
  • 应用程序现在启动,但不幸的是,每当我持久化请求时,hibernate 都会尝试将新文章插入数据库,这会导致主键违规,因为文章已经存在。你也知道解决方法吗?