使用单个@JoinColumn的@OneToMany关系?

时间:2013-04-25 20:44:29

标签: java hibernate jpa orm

我有以下表格(仅显示最重要的列,A& B不是实名btw):

table A {
  ...
}

table B {
  ...
}

table METADATA {
  KEY
  VALUE
  REF_A
  REF_B
}

METADATA为表A和表A保存了额外的键/值元数据。 B.需要键/值,因为我们必须处理动态数据,我们无法事先为A和B创建列。

实体设置为(JPA使用hibernate作为提供者):

interface Entity {
  ...
  getId()
  ...
}

class A implements Entity {
  ...
  @OneToMany(cascade = {ALL}, mappedBy = "a", orphanRemoval = true, fetch = LAZY)
  private List<MetaData> metaData;
  ...
  @Override
  public List<MetaData> getMetaData() {
    return metaData;
  }
  ...
}

class B implements Entity {
  ...   
  @OneToMany(cascade = {ALL}, mappedBy = "b", orphanRemoval = true, fetch = LAZY)
  private List<MetaData> metaData;
  ...
  @Override
  public List<MetaData> getMetaData() {
    return metaData;
  }
  ...
}

class MetaData implements Entity {
  ...
  @ManyToOne
  @JoinColumn(name = "REF_A", nullable = true)
  private A a;

  @ManyToOne
  @JoinColumn(name = "REF_B", nullable = true)
  private B b;
  ...
}

此设置正常。但是,我们在一些数据库(例如DB2)上遇到了一些问题,我们创建了一个唯一的索引(以确保元键只对A或B中的给定行使用一次):

CREATE UNIQUE INDEX METADATA_UNIQUE_KEY ON METADATA (METAKEY, REF_A, REF_B)

因为创建索引要求所有列都是非空的。这不适用于上述设计,因为域逻辑将是元数据在A或B上设置,因此其中一个将始终为空。

当然可能的解决方案是将METADATA分成两个表,一个用于A,一个用于B.但是我宁愿保留一个表,而只是有一个“REF”列,它可以是A或B以及TYPE列,它是否是A或B的元数据。由于我们为每个表都有单独的id序列,因此需要TYPE,A和B可以获得相同的技术ID,从而得到混合的数据否则。

我的问题是 - 有没有办法用JPA设置?

对于基于单表的继承,有一个@DiscriminatorValue可用于区分特定存储的子类,这里也可以使用它吗?我正在寻找类似的东西:

table A {
  ...
}

table B {
  ...
}

table METADATA {
  KEY
  VALUE
  REF
  TYPE
}


@DiscriminatorValue("A")
class A implements Entity {
  ...
  @OneToMany(cascade = {ALL}, mappedBy = "entity", orphanRemoval = true, fetch = LAZY)
  private List<MetaData> metaData;
  ...
  @Override
  public List<MetaData> getMetaData() {
    return metaData;
  }
  ...
}

@DiscriminatorValue("B")
class B implements Entity {
  ...   
  @OneToMany(cascade = {ALL}, mappedBy = "entity", orphanRemoval = true, fetch = LAZY)
  private List<MetaData> metaData;
  ...
  @Override
  public List<MetaData> getMetaData() {
    return metaData;
  }
  ...
}

class MetaData implements Entity {
  ...
  @ManyToOne
  @JoinColumn(name = "REF", nullable = true)
  private Entity entity;

  @DiscriminatorColumn(name="TYPE", discriminatorType=STRING, length=20)      
  private String type;
  ...
}

所以基本上当为A插入元数据时,将使用此SQL:

INSERT INTO METADATA (KEY, VALUE, REF, TYPE) VALUES ("metaKey", "metaValue", 1, "A")

欢迎任何建议。

RGS,

-Martin

1 个答案:

答案 0 :(得分:1)

我不确定为什么你需要在元数据表中创建一个密钥(metakey),因为这些行已经与表A或表B相关联。

但是我认为问题在于将MetaData表视为一个实体,因为它的唯一目的是保存现有实体的一些额外信息,这意味着在TableA或TableB中没有行的MetaData中不能有一行

不使用关系映射,而是使用元素集合直接在相应的实体中使用键/值对的映射:

@Entity
@Table(name="TableA")
public class TableA
{
    @Id
    @GeneratedValue(strategy= GenerationType.TABLE)
    private int id;

    @ElementCollection
    @CollectionTable(name="MetaData", joinColumns={@JoinColumn(name="TableA_id")})
    @MapKeyColumn(name="metaKey")
    @Column(name="metaValue")
    private Map<String, String> metadata;
}

@Entity
@Table(name="TableB")
public class TableB
{
    @Id
    @GeneratedValue(strategy= GenerationType.TABLE)
    private int id;

    @ElementCollection
    @CollectionTable(name="MetaData", joinColumns={@JoinColumn(name="TableB_id")})
    @MapKeyColumn(name="metaKey")
    @Column(name="metaValue")
    private Map<String, String> metadata;
}

请注意,&#34; MetaData&#34;没有java类。表或实体,表从@ElementCollection和@CollectionTable注释自动映射。

以上映射对应于以下MetaData表:

+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| TableA_id | int(11)      | YES  | MUL | NULL    |       |
| metaValue | varchar(255) | YES  |     | NULL    |       |
| metaKey   | varchar(255) | YES  |     | NULL    |       |
| TableB_id | int(11)      | YES  | MUL | NULL    |       |
+-----------+--------------+------+-----+---------+-------+

如果您希望为MetaData保留一个单独的java类来继续使用List而不是Map,那么也可以使用@ElementCollection来完成,您只需要使用@Embeddable而不是@Entity来注释MetaData类。通过这种方式,它不像常规实体那样需要Id列。