如何在@EmbeddedId属性中使用Hibernate的@Filter注释

时间:2019-02-01 19:37:38

标签: java hibernate jpa hql hibernate-filters

tl; dr

无法使Hibernate过滤器与嵌入式id属性一起使用。

示例项目以重现问题here

实际问题

我在这个查询上苦苦挣扎了很长时间。

假设以下实体映射示例:

@Entity
class Client {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "client_id")
    @Basic(optional = false)
    private Integer id;

    @Basic(optional = false)
    private String name;

    @OneToMany(mappedBy = "client")
    private List<CarRent> rentHistory;

    // ... getters and setters
}

@Entity
class Car {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "car_id")
    @Basic(optional = false)
    private Integer id;

    @Basic(optional = false)
    private String foo;

    // ... getters and setters
}

@Entity
class CarRent {
    @EmbeddedId
    private CarRentKey carRentKey;

    @MapsId("clientId")
    @ManyToOne()
    @JoinColumn(name = "client_id", nullable = false, insertable = false, updatable = false)
    private Client client;

    @MapsId("carId")
    @ManyToOne()
    @JoinColumn(name = "car_id",  nullable = false, insertable = false, updatable = false)
    private Car car;

    @Basic(optional = false)
    private String bar;

    // ... getters and setters
}

@Embeddable
class CarRentKey {
    private int clientId;
    private int carId;
    @Column(name = "date_due")
    private Date dateDue;

    // ... getters and setters
}

我需要从特定日期获取所有带有CarRent填充的rentHistory的客户端。以下查询对我来说将非常适用:

from Client cl
    left outer join fetch c.rentHistory as rent with rent.car = c and rent.dateDue = :date

但是Hibernate一直告诉我在获取异常中的联接时要使用过滤器。

我尝试了

@Entity
@FilterDef(name="dateDueFilter", parameters= {
    @ParamDef( name="dateDue", type="date" ),
})
@Filters( {
    @Filter(name="dateDueFilter", condition="dateDue = :dateDue"),
})
class CarRent {
    // ...
}

但是当我运行查询时,就像这样:

EntityManager em;
// ...
Session hibernateSession = em.unwrap(Session.class);
hibernateSession.enableFilter("dateDueFilter").setParameter("dateDue", dateDue);
em.createQuery("from Client cl"
    + "left outer join fetch c.rentHistory");
List<Client> clientList = q.getResultList();
// clientList contains CarRent of all dates

过滤器只是被忽略。与condition="carRentKey.dateDue = :dateDue"condition="date_due = :dateDue"相同的结果。

我对同一查询中的其他左外部联接使用过滤器,它们工作得很好。但是,这种关系会演化出嵌入式参数,我找不到找到使之起作用的方法。

有可能吗?有其他选择吗?

PS:在where部分进行过滤,例如from Client cl left outer join fetch c.rentHistory as rent where rent.dateDue is null or rent.dateDue = :date是不可行的,因为我的真实查询还有其他联接,结果过滤后会变得很慢。

2 个答案:

答案 0 :(得分:1)

我不知道,如果我理解正确,但是:

em.createQuery("from Client cl"
+ "left outer join fetch c.rentHistory");

不使用应该过滤的@Embeddable

编辑:

您需要使用设置的Session。用它打开交易并查询结果:

hibernateSession.openTransaction();
results = hibernateSession.createQuery(...).list();
hibernateSession.close();

答案 1 :(得分:1)

结果是我在这种情况下误用了@Filter注释。

Vlad Mihalcea帮助我解决了my post in the Hibernate forum上的问题

基于他的定位,我设法使用不带获取和结果转换器的联接查询来解决不带过滤器的问题。

该示例的解决方案是:

org.hibernate.query.Query<Client> q = em.createQuery("select cl, cr " + 
        "from Client cl " + 
        "join CarRent cr on cr.client = cl and cr.carRentKey.dateDue = :date")
        .setParameter("date", exampleDate)
        .unwrap(org.hibernate.query.Query.class).setResultTransformer(new BasicTransformerAdapter() {
            @Override
            public List transformList(List list) {
                Map<Serializable, Client> clientMap =
                        new LinkedHashMap<>(list.size());

                for (Object entityArray: list) {
                    if (Object[].class.isAssignableFrom(entityArray.getClass())) {
                        Client client = null;
                        CarRent carRent = null;

                        Object[] tuples = (Object[]) entityArray;

                        for (Object tuple : tuples) {
                            em.detach(tuple);

                            if (tuple instanceof Client) {
                                client = (Client) tuple;
                            }
                            else if (tuple instanceof CarRent) {
                                carRent = (CarRent) tuple;
                            }
                            else {
                                throw new UnsupportedOperationException(
                                    "Tuple " + tuple.getClass() + " is not supported!"
                                );
                            }
                        }

                        if (client != null) {
                            if (!clientMap.containsKey(client.getId())) {
                                clientMap.put(client.getId(), client);
                                client.setRentHistory(new ArrayList<>());;
                            }
                            if (carRent != null) {
                                client.getRentHistory().add(carRent);
                            }
                        }
                    }
                }
                return new ArrayList<>(clientMap.values());
            }
        });

List<Client> clientList = q.getResultList();