当关系走向另一个方向时,如何使用CriteriaBuilder编写左外连接?

时间:2014-09-11 13:38:12

标签: jpa left-join jpa-2.0 criteria builder

我正在使用JPA 2.0,Hibernate 4.1.0.Final和MySQL 5.5.37。我有以下两个实体......

@Entity
@Table(name = "msg")
public class Message
{

    @Id
    @NotNull
    @GeneratedValue(generator = "uuid-strategy")
    @Column(name = "ID")
    private String id;

    @Column(name = "MESSAGE", columnDefinition="LONGTEXT")
    private String message;

@Entity
@Table(name = "msg_read", uniqueConstraints = { @UniqueConstraint(columnNames = { "MESSAGE_ID", "RECIPIENT" }) })
public class MessageReadDate
{

    @Id
    @NotNull
    @GeneratedValue(generator = "uuid-strategy")
    @Column(name = "ID")
    private String id;

    @ManyToOne
    @JoinColumn(name = "RECIPIENT", nullable = false, updatable = true)
    private User recipient;

    @ManyToOne
    @JoinColumn(name = "MESSAGE_ID", nullable = false, updatable = true)
    private Message message;

    @Column(name = "READ_DATE")
    private java.util.Date readDate;

使用CriteriaBuilder,我该如何写这个

SELECT DISTINCT m.*
FROM msg AS m
LEFT JOIN msg_read AS mr
        ON mr.message_id = m.id AND mr.recipient = 'USER1'

?我的问题是我的Message实体中没有字段“msg_read”,我不确定如何在CriteriaBuilder中指定左外连接的“AND”部分。

5 个答案:

答案 0 :(得分:3)

你可以这样做。

final Root<Message> messageRoot = criteriaQuery.from(Message.class);
Join<Message, MessageReadDate> join1 = messageRoot .join("joinColumnName", JoinType.LEFT);

Predicate predicate = criteriaBuilder.equal(MessageReadDate.<String> get("recipient"), recepientValue;
criteria.add(predicate);
criteriaQuery.where(predicate);
criteriaQuery.distinct(true);

我希望这会解决您的问题。

答案 1 :(得分:1)

如果两个实体之间没有明确的关系(没有双向关联),那么JOIN条件将放在WHERE子句中。

JPQL方法可能如下所示:

SELECT DISTINCT m 
FROM Message m, MessageReadDate mr 
WHERE m.id = mr.message.id AND mr.recipient.name = 'USER1'

因此,Criteria API可能如下所示:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Message> cq = cb.createQuery(Message.class);
Root<Message> m = cq.from(Message.class);
Root<MessageReadDate> mr = cq.from(MessageReadDate.class);

Predicate p1 = cb.equal(mr.get("recipient").get("name"), cb.literal("USER1"));
Predicate p2 = cb.equal(mr.get("message").get("id"), m.get("id"));
Predicate criteria = cb.conjunction();
criteria = cb.and(criteria, p1);
criteria = cb.and(criteria, p2);

cq.where(criteria);
cq.select(m).distinct(true);

List<Message> messages = em.createQuery(cq).getResultList();

答案 2 :(得分:1)

javax.persistence.criteria.Join.on()来自JPA 2.1。依Hibernate开发人员的说法,仍然不允许您加入无关的实体:

https://vladmihalcea.com/how-to-join-unrelated-entities-with-jpa-and-hibernate/

仅当使用JPQL或HQL时,才可以加入不相关的实体。使用JPA Criteria API时,此功能不可用。

进行连接的唯一方法是向实体添加缺失的关联(可能为LAZY),因为Criteria API不允许:root.join("id", JoinType.LEFT),失败的原因是:

org.hibernate.query.criteria.internal.BasicPathUsageException:
Cannot join to attribute of basic type

并且没有运气可以覆盖与Join.on()的连接。

另一种方法是使用JPQL。如果您的查询是动态的,请使用String.format()不安全但较旧的方法来构建JPQL查询))

替代方法是在其他实体上建立根并进行RIGHT JOIN!

答案 3 :(得分:1)

实体“消息”没有对 MessageReadDate 的任何引用。 因此最好使用 start 'MessageReadDate' 作为根,然后转到 'Message'。

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Message> query = cb.createQuery(Message.class);
Root<MessageReadDate> root = query.from(MessageReadDate.class);
final Join<MessageReadDate, Message> msg = root.join("message");

query.select(root);
query.where(cb.equal(root.get("recipient"),
                              "USER1"));
List<Message> = em.createQuery(query)
                  .getResultList();

答案 4 :(得分:0)

您不能在JPA 2.0中执行此操作(在条件查询中)。您必须通过JPQL来完成,或者升级到JPA 2.2。在JPA 2.2中,他们引入了.on()运算符,该运算符允许进行任意外部联接。