解決 Hibernate SyntaxException:輸入處的標記“*”,沒有可行的替代方案
1. 概述
了解使用 Hibernate 時常見的錯誤對於除錯至關重要,同時也有助於養成更好的編碼習慣。這其中一部分需要了解不同環境和語法規則之間的差異。例如,在嘗試選擇多個條目時,我們可能傾向於使用星號* 。然而,在某些情況下,這種假設可能會誤導我們。
在本簡短教學中,我們將逐步講解如何修復 Hibernate 異常SyntaxException: token '*', no viable alternative at input 。首先,我們將分析導致 Hibernate 拋出此異常的原因。然後,透過簡單的範例,我們將示範如何重現此異常,以及最重要的——如何解決它。
2. 使用SELECT *
通常情況下, SyntaxException異常意味著 Hibernate 無法理解給定的查詢語句,原因在於查詢語句的編寫方式。 Hibernate 要求查詢語句必須遵循精確的語法。如果發現任何不符合規範的語法,解析就會失敗,Hibernate 會拋出SyntaxException 。
例如,我們經常使用SELECT * FROM Table來選擇 SQL 中的所有資料。但是,在這種情況下,HQL 會拋出SyntaxException: “token '*', no viable alternative at input” 。
問題不在於 Hibernate 無法理解取得所有欄位的概念。誤解源自於HQL 處理的是物件和字段,而不是表格和列。這就是為什麼 HQL 中沒有字面值*的原因。
3. 重現SyntaxException
既然我們知道了導致SyntaxException原因,我們就應該能夠重現它了。
為此,我們來考慮一下Person JPA 實體類別:
@Entity
public class Person {
@Id
private int id;
private String firstName;
private String lastName;
}
簡而言之,每個人都有ID、名字和姓氏。 ` @Entity註解告訴 JPA Person類別是一個實體,而@Id註解標記了它的主鍵。此外, @Column註解將每個欄位對應到資料庫中對應的欄位。
現在,讓我們嘗試在 HQL 查詢中使用*來獲取所有數據,看看會發生什麼:
class SyntaxExceptionUnitTest {
private static Session session;
@BeforeAll
static void beforeAll() {
session = HibernateUtil.getSessionFactory()
.openSession();
session.beginTransaction();
}
@AfterAll
static void afterAll() {
session.close();
}
@Test
void whenUsingInvalidHQLSyntax_thenThrowSyntaxException() {
assertThatThrownBy(() -> {
session.createQuery("SELECT * FROM Person p", Person.class)
.list();
}).hasRootCauseInstanceOf(SyntaxException.class)
.hasMessageContaining("token '*', no viable alternative");
}
}
正如我們所看到的,嘗試在 HQL 中使用SELECT *會導致 Hibernate 失敗,並拋出SyntaxException: token '*', no viable alternative at input 。
正如我們之前提到的,Hibernate 不知道*的含義,因為它運行在更高的抽象層次。它的查詢圍繞著實體及其映射字段展開,而不是圍繞表和列。
4. 使用SELECT p進行更正
由於設計和底層範式有根本差異,HQL 對星號的解釋方式與 SQL 不同。此外, SQL 處理的是表格和列,而 HQL 處理的是物件和屬性。
這就是 HQL 語法不同的原因。它不像 SQL 那樣請求表示all columns的*符號,而是直接查詢實體本身。因此,最直接的解決方案是使用有效的 HQL 語法來取得所有Person實體:
@Test
void whenUsingValidHQLSyntax_thenCorrect() {
Person person = new Person(1, "Jane", "Austen");
session.persist(person);
List<Person> personList = session.createQuery("SELECT p FROM Person p", Person.class).list();
assertThat(personList).contains(person);
}
如上所示,我們可以使用別名p來表示Person實體,從而選擇整個實體物件。我們可以將其視為一種快捷方式,以便稍後引用Person欄位。
值得注意的是,HQL 還提供了另一種語法快捷方式,可以在不使用別名的情況下編寫相同的查詢,以便選擇所有資料:
session.createQuery("FROM Person", Person.class)
簡而言之,我們可以完全省略 SELECT 子句。
5. 結論
在這篇短文中,我們解釋了 Hibernate 為何會拋出SyntaxException: token '*', no viable alternative at input 。然後,我們透過一個範例重現了該異常,並提供了一個實用的解決方案。