JPA CriteriaBuilder使用@MapKeyColumn生成Map为空的查询

时间:2016-10-26 10:11:30

标签: java hibernate jpa eclipselink jpql

在实体的参考情况之后:

碰撞 - > CollisionStatus< - > CollisionWorkgroup(加入实体) - >工作组

Collision.java:

@Entity
@Table( name = "..." )
public class Collision
{
    ...

    @OneToOne( fetch = FetchType.EAGER, optional = false )
    @JoinColumn( name = "CSSTATE_ID", referencedColumnName = "CSSTATE_ID" )
    private CollisionStatus collisionStatus;

    ...
}

CollisionStatus.java:(感兴趣的课程,见下文)

@Entity
@Table( name = "..." )
public class CollisionStatus
{
    ...

    // THIS IS THE MAPPING OF INTEREST:
    @OneToMany( mappedBy = "collisionStatus", fetch = FetchType.LAZY )
    @MapKeyColumn( name = "CLIENT_ID", insertable = false, updatable = false )
    private Map<Long, CollisionWorkgroup> collisionWorkgroups;

    ...
}

CollisionWorkgroup.java:(在CollisionStatusWorkgroup之间加入实体/表,PK = [CollisionStatusId,ClientId],类型Long)< / p>

@Entity
@Table( name = "..." )
public class CollisionWorkgroup
{
    @EmbeddedId
    protected CollisionWorkgroupEmbeddedPK pk;

    @MapsId( "collisionStatusId" )
    @JoinColumn( name = "CSSTATE_ID", referencedColumnName = "CSSTATE_ID" )
    private CollisionStatus     collisionStatus;

    @MapsId( "clientId" )
    @ManyToOne
    @JoinColumn( name = "CLIENT_ID", referencedColumnName = "CLIENT_ID" )
    private Client              client;

    @ManyToOne( fetch = FetchType.LAZY, optional = false )
    @JoinColumn( name = "WORKGROUP_ID", referencedColumnName = "WORKGROUP_ID" )
    private Workgroup           workgroup;

    ...
}

CollisionWorkgroupEmbeddedPK.java:(加入实体PK类)

@Embeddable
public class CollisionWorkgroupEmbeddedPK implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Column( name = "CSSTATE_ID" )
    private Long              collisionStatusId;

    @Column( name = "CLIENT_ID" )
    private Long              clientId;

    ...
}

Workgroup.java :(实际上不是很有趣,只是用来比较条件查询中的clientId)

@Entity
@Table( name = "..." )
public class Workgroup
{
    @Column( name = "CLIENT_ID", insertable = false, updatable = false )
    private Long              clientId;

    @ManyToOne( fetch = FetchType.EAGER, optional = false )
    @JoinColumn( name = "CLIENT_ID", referencedColumnName = "CLIENT_ID" )
    private Client            client;

    ...
}

这里有趣的映射是从CollisionStatus到加入实体CollisionWorkgroup,以使用@MapKeyColumn连接工作组。

这意味着,每个客户端都可以将一个工作组与一个冲突的状态实体关联起来。登录到系统的每个用户只能从其客户端看到一个用户(每个用户属于一个客户端),但其他用户不会在用户界面上看到。

按原样执行查询时,多个客户端已设置工作组的任何冲突状态都会在我们的数据表/查询结果中生成其他结果。

这是有道理的,因此我需要在查询中添加一些自定义谓词,以便仅生成具有当前用户的客户端ID关联的条目。显示其他客户的条目是错误的。

我在SO上发现了这个:

Using JPA CriteriaBuilder to generate query where attribute is either in a list or is empty

我尝试了代码:

@Override
protected List<Predicate> createCustomPredicates( CriteriaBuilder builder, From<?, ?> root )
{
    List<Predicate> predicates = new ArrayList<>();

    Long clientId = this.sessionHelper.getCurrentClientId();
    Join<Collision, CollisionStatus> collisionStatus = root.join( "collisionStatus" );
    MapJoin<CollisionStatus, Long, CollisionWorkgroup> collisionWorkgroups = collisionStatus.<CollisionStatus, Long, CollisionWorkgroup>joinMap( "collisionWorkgroups", JoinType.LEFT );
    predicates.add( builder.and( builder.or( builder.isEmpty( collisionWorkgroups ),
                                             builder.equal( collisionWorkgroups.<String>get( "workgroup" ).<String>get( "clientId" ), clientId ) ) ) );

    ...
}

这给我builder.isEmpty(上的编译错误说:

Bound mismatch: The generic method isEmpty(Expression<C>) of type CriteriaBuilder
is not applicable for the arguments (MapJoin<CollisionStatus,Long,CollisionWorkgroup>).
The inferred type CollisionWorkgroup is not a valid substitute for the bounded
parameter <C extends Collection<?>>

显然,问题是Map不是Collection的子类。

问:

如何使用Criteria API在JPA中测试空Map

1 个答案:

答案 0 :(得分:2)

是的,因为Map不是Collection。就Criteria API而言,JPA仅根据Collection定义IS_EMPTY谓词。由于这个以及相关的CriteriaBuilder方法签名,您将无法通过JPA的CriteriaBuilder执行此操作。

真的JPA应该为该方法添加一个重载:

// existing method
<C extends Collection<?>> Predicate isEmpty(Expression<C> collection);
// overload
<C extends Map<?>> Predicate isEmpty(Expression<C> map);

JPQL应该可以工作。