自hibernate 6以来,使用test on left join on where子句从HQL生成了不正确的SQL where子句

nszi6y05  于 2023-04-30  发布在  其他
关注(0)|答案(2)|浏览(119)

因为我迁移到Hibernate 6(5.4.30分,最后到6分。2.0.Final),我的一些查询翻译不正确。
以下是基于this tutorial的最小复制项目:
实体(省略getter/setter):

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;
}

@Entity
public class PublishingHouse {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;
}

一本书有一个指向作者和出版社的外键:

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String title;
    
    @ManyToOne
    private Author author;
    
    @ManyToOne
    private PublishingHouse publishingHouse;
}

我的主要方法是添加实体,然后尝试查找所有以“Stephen King”为作者或以“Simon & Schuster”为出版社的书籍:

public static void main(String[] args) {
        Transaction transaction = null;
        PublishingHouse phSimonSchuster = null;
        Author stephenKing = null;
        
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            
            
            /**
             *  Add Publishing houses
             */
        
            phSimonSchuster = new PublishingHouse();
            phSimonSchuster.setName("Simon & Schuster");
            session.persist(phSimonSchuster);
            
            var phHarperCollins = new PublishingHouse();
            phHarperCollins.setName("HarperCollins");
            session.persist(phHarperCollins);
            
            
            /**
             * Add Authors
             */
            
            stephenKing = new Author();
            stephenKing.setName("Stephen King");
            session.persist(stephenKing);
            
            var hpLovecraft = new Author();
            hpLovecraft.setName("Howard Phillips Lovecraft");
            session.persist(hpLovecraft);
            
            
            /**
             * Add Books
             */
            
            var bShining = new Book();
            bShining.setTitle("Shining");
            bShining.setAuthor(stephenKing);
            bShining.setPublishingHouse(phSimonSchuster);
            session.persist(bShining);
            
            var bColorOutOfSpace = new Book();
            bColorOutOfSpace.setTitle("The Colour Out of Space");
            bColorOutOfSpace.setAuthor(hpLovecraft);
            bColorOutOfSpace.setPublishingHouse(phHarperCollins);
            session.persist(bColorOutOfSpace);
        
            transaction.commit();
        }
        
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            
            var hql = "SELECT book "
                    + "FROM Book book "
                    + "   LEFT JOIN book.author author "
                    + "        WITH author.id = :stephenKingId "
                    + "   LEFT JOIN book.publishingHouse publishingHouse "
                    + "        WITH publishingHouse.id = :simonSchusterId "
                    + "WHERE author IS NOT NULL OR publishingHouse IS NOT NULL ";
            
            List<Book> books = session.createQuery(hql, Book.class)
                    .setParameter("stephenKingId", stephenKing.getId())
                    .setParameter("simonSchusterId", phSimonSchuster.getId())
                    .list();
            books.forEach(b -> {
                System.out.println("Book found: " + b.getTitle());
            });
        }
        
    }

我的main方法的输出是:
专辑中文名:Shining
首页〉外文书〉文学〉西洋文学〉The Colour Out of Space
但是,《太空中的颜色》不是斯蒂芬·金写的,也不是由“西蒙与舒斯特”出版的,正如你在我插入数据时看到的,所以这本书不应该通过这个查询得到。
问题是这个hql:

SELECT book
FROM Book book
   LEFT JOIN book.author author
        WITH author.id = :stephenKingId
   LEFT JOIN book.publishingHouse publishingHouse
        WITH publishingHouse.id = :simonSchusterId
WHERE author IS NOT NULL OR publishingHouse IS NOT NULL

翻译错误:

SELECT b1_0.id,
       b1_0.author_id,
       b1_0.publishinghouse_id,
       b1_0.title
FROM   book b1_0
       LEFT JOIN author a1_0
              ON a1_0.id = b1_0.author_id
             AND b1_0.author_id = ?
       LEFT JOIN publishinghouse p1_0
              ON p1_0.id = b1_0.publishinghouse_id
             AND b1_0.publishinghouse_id = ?
WHERE  b1_0.author_id IS NOT NULL
   OR b1_0.publishinghouse_id IS NOT NULL

为什么是错的?因为WHERE条件正在测试author_idpublishinghouse_id是否不为null,不是在LEFT JOIN上,而是在Book实体上。当然,每本书都有一个author_idpublishinghouse_id,所以每本书都可以在查询中检索。但是在HQL查询中,测试是在LEFT JOIN上。还有一个问题:在HQL中,WITH子句正在测试LEFT JOIN别名的ID,因此:

FROM book book
LEFT JOIN book.author author
     WITH author.id = :stephenKingId

应为:

FROM book b1_0
LEFT JOIN author a1_0
       ON a1_0.id = b1_0.author_id
      AND a1_0.id = ?

而不是

FROM book b1_0
LEFT JOIN author a1_0
       ON a1_0.id = b1_0.author_id
      AND b1_0.author_id = ?

最后,正确的查询应该是:

SELECT b1_0.id,
       b1_0.author_id,
       b1_0.publishinghouse_id,
       b1_0.title
FROM   book b1_0
       LEFT JOIN author a1_0
              ON a1_0.id = b1_0.author_id
             AND a1_0.id = ?
       LEFT JOIN publishinghouse p1_0
              ON p1_0.id = b1_0.publishinghouse_id
             AND p1_0.id = ?
WHERE a1_0.id IS NOT NULL OR p1_0.id IS NOT NULL

此查询只查找以“斯蒂芬·金”为作者或以“西蒙与舒斯特”为出版社的书籍
我在Hibernate版本〈6时没有这个问题。0

tf7tbtn2

tf7tbtn21#

我建议一个更简单的查询:

SELECT b.id, b.author.id, b.publishingHouse.id, b.title 
FROM Book b 
LEFT JOIN b.author a 
LEFT JOIN b.publishingHouse p 
WHERE a.id = :authorId OR p.id = :publishingHouseId

您可以使用WITH:

SELECT b.id, b.author.id, b.publishingHouse.id, b.title
FROM Book b
LEFT JOIN b.author a WITH a.id = :authorId
LEFT JOIN b.publishingHouse p WITH p.id = :publishingHouseId
WHERE a.id IS NOT NULL OR p.id IS NOT NULL

或者,显式指定连接条件:

SELECT b.id, b.author.id, b.publishingHouse.id, b.title 
FROM Book b 
LEFT JOIN b.author a ON a.id = :authorId 
LEFT JOIN b.publishingHouse p ON p.id = :publishingHouseId 
WHERE a.id IS NOT NULL OR p.id IS NOT NULL
46qrfjad

46qrfjad2#

我的意思是,如果我理解正确的话,您正在尝试使用is not null条件来模拟两个常规内部连接的效果。
为什么不首先使用内部联接来编写查询呢?
或者:

SELECT book
FROM Book book
   JOIN book.author author
       WITH author.id = :stephenKingId
   JOIN book.publishingHouse publishingHouse
       WITH publishingHouse.id = :simonSchusterId

甚至只是:

SELECT book
FROM Book book
   JOIN book.author author
   JOIN book.publishingHouse publishingHouse
WHERE author.id = :stephenKingId 
  AND publishingHouse.id = :simonSchusterId

我的意思是,我只是不确定为什么会在这里使用外连接。

更新

我错过了限制中的or。在这种情况下,类似于:

SELECT book
FROM Book book
   JOIN book.author author
WHERE author.id = :stephenKingId 
UNION
SELECT book
FROM Book book
   JOIN book.publishingHouse publishingHouse
WHERE publishingHouse.id = :simonSchusterId

或者类似的东西

更新2

实际上,我已经完全迟钝了!此查询完全避免了连接:

FROM Book book
WHERE book.author.id = :stephenKingId 
   OR book.publishingHouse.id = :simonSchusterId

(我 * 觉得 * 原始查询有什么东西很臭,但一开始我没能把手指放在上面。)

相关问题