Java 如何使用涉及@JoinFormula的反向@OneToOne映射保持?

Java 如何使用涉及@JoinFormula的反向@OneToOne映射保持?,java,hibernate,jpa,Java,Hibernate,Jpa,我试图在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

我试图在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\u class
binary.entity\u id
组成。这是一个我现在必须接受的结构,它似乎在这里引起混乱。列
binary.entity\u id
引用聚合表的主键,而
binary.entity\u 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;
}
但我在坚持容器性方面遇到了问题:

  • 如果我只是指定
    CascadeType.PERSIST
    Hibernate无法设置
    binary.entity\u id
  • 如果我没有级联持久化,我不知道什么时候自己设置
    binary.entity\u id
    ,如何持久化映射对象,结果是:

    org.hibernate.transientObject异常:对象引用未保存的临时实例-在刷新之前保存临时实例:ContainerEntity.binary->binary

换言之,我希望但目前无法保持这两个实体:

containerEntity = new ContainerEntity();
containerEntity.setBinary( new Binary() );
entityManager.persist(containerEntity);
有什么想法或有用的建议吗



关于赏金的注意事项:这个问题还没有答案,我可以接受为“正确”,尽管还有一个提示我将在下周查看。但是,我的奖励时间已经结束,因此我将奖励给目前为止最接近的答案。

您没有切换join列名和referenceColumnName吗? 像这样:

@JoinColumn(name = "entity_id", referencedColumnName = "id", insertable=false, updatable=false))
@Entity
public class ContainerEntityA {
    @PostPersist
    public void copyIdToBinary() {
        binary.setEntityId(id);
    }
}
Idea1(不工作): 您是否尝试从
@JoinColumn
中删除
insertable=false
(即
insertable
不应该为true?)并在@OneToOne注释中添加mappingBy=“entityId”

Idea2:(您可以更改实体的保存顺序)

1.移除级联

二,

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

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

您是否已决定自动生成ID?如果没有,您可以在应用程序代码中为容器实体生成ID,然后再进行持久化,并在附加这些ID时将其复制到它们的
二进制
对象中

如果需要自动生成,可以尝试级联persist,并使用
ContainerEntityA
上的生命周期回调方法将生成的ID复制到
二进制文件中。像这样:

@JoinColumn(name = "entity_id", referencedColumnName = "id", insertable=false, updatable=false))
@Entity
public class ContainerEntityA {
    @PostPersist
    public void copyIdToBinary() {
        binary.setEntityId(id);
    }
}
您可以确保此方法将看到ID的生成值;状态(在第3.5节“实体侦听器和回调方法”中)为:

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

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

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

以及:

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


所以,这不是一个很好的解决方案。它不便于携带,也可能不起作用。但是它有可能适用于您的特定版本的Hibernate,如果它适用,它可能是您可以在不更改模式的情况下使用的最干净的解决方案。

好的,请尝试以下我认为非常有效的解决方案。我已经测试并可以按预期加载和保存容器和关联实体

首先,容器必须从一些公共实体扩展:

@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();
}
以下测试按预期通过:

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>


否,我确信Join列是正确的。毕竟,Hibernate在访问现有的ContainerEntity时正确地读取和填充了ContainerEntity的二进制属性。您能编辑包含两个类的所有映射的包含代码,以及创建和持久化新实体的代码吗?@AlanHay我总是尽量简洁。但是你是对的,我可能遗漏了太多,所以我更新了这个问题。你代码的上半部分将保留这两个实体。这基本上是我发现的唯一方法(还有变体),而且不太方便。另外,如果在
$entityManager.persist($containerEntity)
之前调用
$containerEntity->set($binary)
,它将引发异常。我也尝试过“Idea1”,但这会导致Hibernate在启动时抱怨:
org.Hibernate.MappingException:实体映射中的重复列:containerEntity列:id(应使用insert=“false”update=“false”进行映射)
Idea2:是的,这不是很方便,但这是一个有效的建议。事实上,如果这些调用的顺序不是您指定的,它会抛出一个异常,但我的代码尊重它,不是吗?我不确定我是否正确理解了您。我尝试在
@JoinColumnsOrFormulas之外添加了一个
@JoinColumnsOrFormulase> .Hibernate似乎忽略了后者,后者会中断读取二进制entit
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>