3

Hibernate で次のテーブル間の関係をマッピングしようとしています。

create table binary (
    id number not null primary key,
    data blob,
    entity_class varchar(255) not null,
    entity_id number not null,
    unique (entity_id, entity_class)
);

create table container_entity (
    id number not null primary key,
    ...
);

バイナリ テーブルは、任意の他のテーブルのバイナリ データを保持することになっています。「外部キー」は、データベース用語ではありませんが、binary.entity_classとで構成されますbinary.entity_id。これは私が今のところ受け入れなければならない構成であり、ここで混乱を引き起こしているようです. 列binary.entity_idは集計テーブルの主キーを参照binary.entity_classし、集計テーブル自体を定義します。

BINARY                               CONTAINER_ENTITY_A  CONTAINER_ENTITY_B 
id  entity_class      entity_id      id                  id                    ...
-------------------------------      ------------------  ------------------
1   ContainerEntityA  1          ->  1                                         ...
2   ContainerEntityB  1          ->                      1
3   ContainerEntityB  2          ->                      2

ContainerEntity のマッピングは、読み取り専用で使用されている場合は既に機能しています。

@Entity @Table(name="container_entity_a")
public class ContainerEntityA {
  @Id @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @OneToOne
  @JoinColumnsOrFormulas({ 
    @JoinColumnOrFormula(column = 
      @JoinColumn(name = "id", referencedColumnName = "entity_id", 
        insertable=false, updatable=false)),
    @JoinColumnOrFormula(formula = 
      @JoinFormula(value = "'ContainerEntityA'", referencedColumnName = "entity_class")) 
  })
  private Binary binary;

  public void setBinary(Binary aBinary) {
    aBinary.setEntityClass("ContainerEntityA");
    this.binary = aBinary;
  }
}

@Entity @Table(name="binary")
public class Binary {
  @Column(name = "entity_id", nullable = false)
  private Long entityId;

  @Column(name = "entity_class", nullable = false)
  private String entityClass;
}

しかし、ContainerEntity の永続化に問題があります:

  • CascadeType.PERSISTHibernateを指定しただけでは、設定に失敗しますbinary.entity_id
  • カスケード永続化しないと、binary.entity_id自分自身をいつ設定するのか、マップされたオブジェクトを永続化する方法がわからず、次のようになります。

    org.hibernate.TransientObjectException: オブジェクトが保存されていない一時インスタンスを参照しています - フラッシュする前に一時インスタンスを保存します: ContainerEntity.binary -> Binary

言い換えれば、私は望んでいますが、現在、次のように両方のエンティティを永続化することに失敗しています:

containerEntity = new ContainerEntity();
containerEntity.setBinary( new Binary() );
entityManager.persist(containerEntity);

アイデアや役立つ提案はありますか?


報奨金に関する注意:この質問に対する「正しい」と受け入れることができる答えはまだありませんが、来週確認するヒントがもう 1 つあります。懸賞金の時間は終わったので、これまでに最も近い回答に賞を授与します.

4

4 に答える 4

2

さて、完璧に機能すると思う次のことを試してください。テストしたところ、コンテナと関連エンティティを期待どおりにロードおよび保存できました。

まず、コンテナはいくつかの共通エンティティから拡張する必要があります:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Container {

    //cannot use identity here however a table or sequence should work so long as
    //the initial value is > current max ids from all container tables.
    @Id
    @TableGenerator(initialValue = 10000, allocationSize = 100, table = "id_gen", name = "id_gen")
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "id_gen")
    private Long id;

    public Long getId() {
        return id;
    }

    public BinaryData getBinaryData() {
        return getData().size() > 0 ? getData().get(0) : null;
    }

    public void setBinaryData(BinaryData binaryData) {
        binaryData.setContainerClass(getName());
        binaryData.setContainer(this);

        this.getData().clear();
        this.getData().add(binaryData);
    }

    protected abstract List<BinaryData> getData();

    protected abstract String getName();
}

コンクリートコンテナA. 関係は OneToMany としてマップする必要がありますが、追加の @Where 句 (およびデータベースの一意のキー) により、効果的にこれが @OneToOne になります。このクラスのクライアントは、それをシングル エンド アソシエーションとして見ることができます。

@Entity
@Table(name = "container_a")
public class ContainerA extends Container {

    @OneToMany(mappedBy = "container", cascade = CascadeType.ALL)
    @Where(clause = "container_class = 'container_a'")
    private List<BinaryData> binaryData;

    public ContainerA() {
        binaryData = new ArrayList<>();
    }

    @Override
    protected List<BinaryData> getData() {
        return binaryData;
    }

    @Override
    protected String getName() {
        return "container_a";
    }
}

コンテナB

@Entity
@Table(name = "container_b")
public class ContainerB extends Container {

    @OneToMany(mappedBy = "container", cascade = CascadeType.ALL)
    @Where(clause = "container_class = 'container_b'")
    private List<BinaryData> binaryData;

    public ContainerB() {
        binaryData = new ArrayList<>();
    }

    @Override
    protected List<BinaryData> getData() {
        return binaryData;
    }

    @Override
    protected String getName() {
        return "container_b";
    }
}

BinaryData から Container への逆マッピングには、Hibernate の @Any マッピングを使用する必要があります。

@Entity
@Table(name = "binary_data")
public class BinaryData {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @OneToOne
    @Any(metaColumn = @Column(name = "container_class"))
    @AnyMetaDef(idType = "long", metaType = "string", metaValues = {
            @MetaValue(targetEntity = ContainerA.class, value = "container_a"),
            @MetaValue(targetEntity = ContainerB.class, value = "container_b") })
    @JoinColumn(name = "entity_id")
    private Container container;

    @Column(name = "container_class")
    private String containerClass;

    public Long getId() {
        return id;
    }

    public Container getContainer() {
        return container;
    }

    public void setContainer(Container container) {
        this.container = container;
    }

    public String getContainerClass() {
        return containerClass;
    }

    public void setContainerClass(String containerClass) {
        this.containerClass = containerClass;
    }
}

次のテストは期待どおりに合格しました。

public class ContainerDaoTest extends BaseDaoTest {

    @Test
    public void testSaveEntityA() {

        ContainerA c = new ContainerA();

        BinaryData b = new BinaryData();
        c.setBinaryData(b);

        ContainerDao dao = new ContainerDao();
        dao.persist(c);

        c = dao.load(c.getId());
        Assert.assertEquals(c.getId(), b.getContainer().getId());
    }

    @Test
    public void testLoadEntity() {
        ContainerA c = new ContainerDao().load(2l);
        Assert.assertEquals(new Long(3), c.getBinaryData().getId());
        Assert.assertEquals(new Long(2), c.getBinaryData().getContainer().getId());
        Assert.assertEquals("container_a", c.getBinaryData().getContainerClass());
    }

    @Override
    protected String[] getDataSetPaths() {
        return new String[] { "/stack/container.xml", "/stack/binarydata.xml" };
    }
}

次のデータセットを使用する場合:

<dataset>
    <container_a id="1" />
    <container_a id="2" />
    <container_b id="1" />
    <container_b id="2" />
</dataset>

<dataset>
    <binary_data id="1" container_class="container_a" entity_id="1" />
    <binary_data id="2" container_class="container_b" entity_id="2" />
    <binary_data id="3" container_class="container_a" entity_id="2" />
    <binary_data id="4" container_class="container_b" entity_id="1" />
</dataset>
于 2013-11-16T14:34:29.540 に答える
0

結合列名と referenceColumnName を入れ替えていませんか?このような:

@JoinColumn(name = "entity_id", referencedColumnName = "id", insertable=false, updatable=false))
于 2013-11-12T17:11:07.327 に答える