简体   繁体   中英

Join optional column with JPA and hibernate

I am working with hibernate through JPA (backend for testing is h2, but the same issue happens on other engines) and have encountered a problem when joining optional columns and filtering on them.

I have the following data model:

@Entity
public class Ticket {
    @Id
    private long id;

    @ManyToOne(optional = true)
    @Nullable
    private Assignee assignee;
}

@Entity
public class Assignee {
    @Id
    private long id;

    private String name;
}

And three entities:

  • Assignee{id = 1, name = kitty}
  • Ticket{id = 1, assignee = null}
  • Ticket{id = 2, assignee = 1}

Now, I am querying tickets with jpql:

  • select t from Ticket t yields both tickets, as expected.
  • select t from Ticket t where t.assignee is null yields ticket 1 only, as expected.
  • select t from Ticket t where t.assignee.name = :name with name=kitty yields ticket 2 only, as expected.

However, linking the two filters together in an OR clause does not behave as expected: select t from Ticket t where (t.assignee is null or t.assignee.name = :name) with name=kitty only yields ticket 2, while the query should match ticket 1 as well (because assignee may be null). When checking the hibernate debug log, the following SQL query is generated:

SELECT
  ticket0_.id          AS id1_1_,
  ticket0_.assignee_id AS assignee2_1_
FROM Ticket ticket0_ CROSS JOIN Assignee assignee1_
WHERE ticket0_.assignee_id = assignee1_.id AND (ticket0_.assignee_id IS NULL OR assignee1_.name = ?)

The condition ticket0_.assignee_id = assignee1_.id is obviously never satisfied for ticket 1 since it has no assignee, so hibernate translated this query incorrectly.

Is there any way for me to fix this?

On your SELECT statement, you indicated this expression t.assignee.name . Though you have not explicitly used a JOIN operation in your SELECT statement, traversing from the Ticket entity to the Assignee entity to get the name property will require a NATURAL JOIN between the 2 entities. Thus, you will see a ticket0_.assignee_id = assignee1_.id in your output SQL.

You can rewrite you query:

SELECT t from Ticket t WHERE (t.assignee IS NULL) OR (t.assignee IS NOT NULL AND t.assignee.name = :name)

Or try using an OUTER JOIN instead:

SELECT t FROM Ticket t LEFT JOIN Assignee a WHERE a.name = :name

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM