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
  • 如果我不 cascade-persist,我不知道什么时候设置binary.entity_id自己,如何持久化映射的对象,我最终得到:

    org.hibernate.TransientObjectException:对象引用了未保存的瞬态实例 - 在刷新之前保存瞬态实例:ContainerEntity.binary -> Binary

换句话说,我想但目前未能像这样坚持这两个实体:

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

有什么想法或有用的建议吗?


关于赏金的注意事项:这个问题还没有答案,我可以接受为“正确”,尽管还有一个提示我将在下周检查。不过,我的赏金时间已经结束,所以我会将其奖励给迄今为止最接近的答案。

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 回答
1

您的问题的根源是 Hibernate 是一个对象关系映射器,而这个数据库并不是真正的关系。具体来说,使用列中的值来引用表名不属于关系模型。理想情况下,您可以通过更改架构来解决此问题,但听起来这是不可能的。

您是否热衷于自动生成 ID?如果没有,您可以在持久化之前在应用程序代码中为容器实体生成 ID,然后Binary在附加它们时将它们复制到它们的对象中。

如果您需要自动生成,您可能会尝试级联持久化,并使用@PostPersist生命周期回调方法ContainerEntityA将生成的 ID 复制到Binary. 像这样:

@Entity
public class ContainerEntityA {
    @PostPersist
    public void copyIdToBinary() {
        binary.setEntityId(id);
    }
}

您可以确定此方法将看到生成的 ID 值;规范声明(在第3.5 节“实体侦听器和回调方法”中):

生成的主键值在 PostPersist 方法中可用。

但是,您不能确定的是,对Binary'entityId字段的更新会在它被持久化之前发生。该规范警告说:

通常,可移植应用程序的生命周期方法不应 [...] 访问其他实体实例

和:

回调方法是在生命周期事件级联到相关实体之前还是之后调用取决于实现。应用程序不应依赖此顺序。

所以,这不是一个很好的解决方案。它不是便携式的,它可能无法正常工作。但它可能会与您的特定版本的 Hibernate 一起使用,如果可以,它可能是您可以在不更改架构的情况下使用的最干净的解决方案。

于 2013-11-16T12:29:34.973 回答
0

你没有切换连接列名和referenceColumnName吗?像这样:

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

Idea1(不起作用):您是否尝试从 @OneToOne 注释中删除insertable=false(即不应该insertable是真的?)@JoinColumn并添加 mappingBy="entityId" ?

Idea2:(您可能会更改实体的保存顺序)。

1.删​​除级联

2.

containerEntity = new ContainerEntity();
entityManager.persist(containerEntity);
binary = new Binary();
containerEntity.set( binary );
entityManager.persist(binary);
entityManager.merge(containerEntity);

....  in ContainerEntity
public void setBinary(Binary aBinary) {
  aBinary.setEntityClass("ContainerEntity");
  aBinary.setEntityId(this.id);
  this.binary = aBinary;
}

Idea3: 例如指定 CascadeType.PERSIST 并将 binary.entityId 设置为 0。将其二进制的@PostPersistonEntityContainer设置entityId为正确的值。这会在短时间内使您的数据库不一致,但如果您在事务中这样做,这不是问题。

于 2013-11-14T15:18:21.220 回答