使用Hibernate Entity Criteria的现有表连接

时间:2017-02-28 21:17:46

标签: java sql hibernate orm hibernate-criteria

假设我有一个如下所示的实体,其中每个集合都是一个与PersonEntity主键具有外键关系的独立实体。

PersonEntity - PK: person_id
   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person", orphanRemoval = true)
   Set<AddressEntity> addresses    
   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person", orphanRemoval = true)
   Set<NameEntity> nameParts       

AddressEntity和NameEntity都有PersonEntity,这是以实体形式表示的FK关系。

所有表格都有一个名为tenant_id的字段,它们被分区。

如果我创建HibernateCriteria,如下所示:

final Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PersonEntity.class, "p");
criteria.add(Restrictions.eq("p.personId", personId));
criteria.add(Restrictions.eq("p.tenantId", tenantId));

我得到的SQL就像:

select ALL_ATTRIBUTES_SNIPPED
FROM person this_
LEFT OUTER JOIN address addresses2_
ON this_.person_id=addresses2_.person_id
LEFT OUTER JOIN name nameparts4_
ON this_.person_id=nameparts4_.person_id
WHERE this_.person_id=?
AND this_.tenant_id=?

查看解释计划,我看到这会在进行连接时检查所有分区。这是不必要的,因为它只需要查看一个分区。

我希望在所有表上添加额外限制,以便所有表都受到tenant_id的限制。所以SQL可能如下所示:

  select ALL_ATTRIBUTES_SNIPPED
    FROM person this_
    LEFT OUTER JOIN address addresses2_
    ON this_.person_id=addresses2_.person_id
    LEFT OUTER JOIN name nameparts4_
    ON this_.person_id=nameparts4_.person_id
    WHERE this_.person_id=?
    AND this_.tenant_id=?
    AND addresses2_.tenant_id =?
    AND nameparts4_.tenant_id =?

但是,我似乎无法弄清楚如何创建标准来执行此操作。当我尝试以下内容时:

final Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PersonEntity.class, "p")
    .createAlias("addresses", "address", JoinType.LEFT_OUTER_JOIN)
    .createAlias("nameParts", "namePart", JoinType.LEFT_OUTER_JOIN)

criteria.add(Restrictions.eq("p.personId", personId));
criteria.add(Restrictions.eq("p.tenantId", tenantId));
criteria.add(Restrictions.eq("address.tenantId", tenantId));
criteria.add(Restrictions.eq("namePart.tenantId", tenantId));

我得到的SQL看起来像这样:

select ALL_ATTRIBUTES_SNIPPED
FROM person this_
LEFT OUTER JOIN address addresses2_
ON this_.person_id=addresses2_.person_id
LEFT OUTER JOIN name nameparts4_
ON this_.person_id=nameparts4_.person_id
LEFT OUTER JOIN address addresses3_
ON this_.person_id=addresses3_.person_id
LEFT OUTER JOIN name nameparts1_
ON this_.person_id=nameparts1_.person_id
WHERE this_.person_id=?
and this_.tenant_id = ?
and addresses3_.tenant_id = ?
and nameparts1_.tenant_id = ?

如您所见,表格连接了两次。

如何创建使用原始表的限制?我不知道如何能够提供访问现有连接的限制。我尝试了p.addresses.tenantId之类的内容,但却说addresses无法识别。

编辑:我已经在很大程度上解决了查询问题,方法是将此行放在Person inntity中的Set和set实体中的PersonEntity上(即AddressEntity)。

@JoinColumns(value={
        @JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID", insertable=false, updatable=false), 
        @JoinColumn(name="TENANT_ID", referencedColumnName="TENANT_ID", insertable=false, updatable=false)
        })

我还删除了这些列的mappedBy属性。

这会强制加入person_id和ten​​ant_id,并使解释计划的成本显着提高(以及实际性能)。但是,我不确定这是否是一个真正的解决方案,因为它引入了一个新问题。

我现在的问题是,当我尝试创建PersonEntity时,我收到以下错误:

12:09:26.672 WARN  [main] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 1400, SQLState: 23000  
12:09:26.672  ERROR [main] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ORA-01400: cannot insert NULL into ("USER"."ADDRESS"."PERSON_ID")

即使SQL显示在尝试插入地址之前发生了人员插入,也会发生这种情况。似乎没有传递person_id以放入地址插入中。我怎么能强迫Hibernate这样做呢?以前,它只是自动发生(从我的角度来看)。

如果重要的话,我正在使用序列生成器来创建主键。

1 个答案:

答案 0 :(得分:0)

为我解决这个问题的方法是将这些行添加到PersonEntity中的集合中,然后添加到子实体类(AddressEntity,NameEntity)中的PersonEntity字段:

@ManyToOne(fetch = FetchType.EAGER)    
@JoinColumns(value={
            @JoinColumn(name = "PERSON_ID", referencedColumnName = "PERSON_ID", nullable = false), 
            @JoinColumn(name = "TENANT_ID", referencedColumnName = "TENANT_ID", nullable = false)
            })
public PersonEntity getPerson() {
        return personEntity;
}

这适用于查询,但我无法进行插入或更新,因此我必须做的另一件事是确保现有的tenantId字段具有insertable = false和updateable = false,如下所示:

@Column(name = "TENANT_ID", insertable = false, updatable = false)
public String getTenantId() {
    return tenantId;
}

然后,执行原始问题中的条件将导致所有子表在PERSON_ID和TENANT_ID上具有连接,正如我想要的那样。

这将解释计划中的估计成本从2525改为15,因为它可以直接进入正确的分区而不是循环遍历它们。