JPA 中 CAST 和 TREAT 的區別
一、簡介
在 JPA 中, CAST和TREAT是兩個不同的關鍵字,用於操作資料類型和實體關係。在本教程中,我們將探討CAST和TREAT之間的差異,並透過範例來說明它們的用法。
2. JPA 中的CAST
JPA 中的CAST運算子主要用於 JPQL 查詢中的類型轉換。它允許我們將值從一種資料類型明確轉換為另一種資料類型。例如,我們可以使用CAST將String轉換為Integer ,反之亦然。
這是CAST的語法:
CAST(expression AS type)
expression是我們要轉換的值或字段, type是我們要將表達式轉換成的目標資料類型。
3. JPA 中的TREAT
相較之下, TREAT運算子旨在對 JPQL 查詢中的實體進行類型安全的向下轉換。在處理繼承層次結構時它特別有用。當我們使用TREAT時,我們指定實體的子類型,JPA 檢查實際實體是否確實屬於該類型。
與CAST不同, TREAT不會更改值的基礎資料類型。相反,它允許我們訪問該值,就像它是目標類型一樣。
這是TREAT的語法:
TREAT(expression AS type)
expression是要處理的值, type是目標資料型態。
4. 目的和用途
在 JPA 查詢中, CAST和TREAT都用於處理類型轉換,但它們的用途不同。
4.1.演員CAST操作員
CAST用於將一種資料類型轉換為另一種資料類型以進行運算或比較。當執行需要與資料庫中儲存的資料類型不同的資料類型的查詢時,通常會使用它。
考慮一個範例,其中一個名為Employee實體,並且salary欄位作為String儲存在資料庫中。以下是我們的Employee實體的定義方式:
@Entity
public class Employee {
@Id
private Long id;
private String salary;
// getters and setters
}
在這個例子中, salary欄位的類型是String ,但是我們可能需要根據這個欄位進行數值運算或比較。為了實現這一點,我們可以使用CAST將salary欄位轉換為Integer類型:
Employee emp1 = new Employee();
emp1.setId(1L);
emp1.setSalary("5000");
em.persist(emp1);
Query query = em.createQuery("SELECT CAST(e.salary AS Integer) FROM Employee e");
List<Integer> salaries = query.getResultList();
assertEquals(5000, salaries.get(0));
在此查詢中,我們使用CAST將salary欄位從String轉換為Integer 。此查詢的結果是表示員工薪資的整數清單。
4.2. TREAT操作員
另一方面, TREAT用於繼承中的類型安全向下轉型。它允許我們以一種承認其實際子類別類型的方式處理實體,即使是透過基底類別引用時也是如此。
假設我們有一個實體Vehicle ,其子類別為Car和Bike ,並且我們希望從選擇 Vehicle 的查詢中僅檢索Car實體Vehicle.我們可以使用TREAT來確保類型安全:
@Entity
public class Vehicle {
@Id
private Long id;
private String type;
// getters and setters
}
@Entity
public class Car extends Vehicle {
private Integer numberOfDoors;
// getters and setters
}
為了實現這一點,我們在 JPQL 查詢中使用TREAT運算子將Vehicle實例轉換為Car實例:
Vehicle vehicle = new Vehicle();
vehicle.setId(1L);
vehicle.setType("Bike");
Car car = new Car();
car.setId(2L);
car.setType("Car");
car.setNumberOfDoors(4);
em.persist(vehicle);
em.persist(car);
Query query = em.createQuery("SELECT TREAT(v AS Car) FROM Vehicle v WHERE v.type = 'Car'");
List<Car> cars = query.getResultList();
assertEquals(4, cars.get(0).getNumberOfDoors());
在此查詢中, TREAT允許我們在適用的情況下將Vehicle視為Car 。結果就是Car實例的列表,即使資料庫中的基礎實體的類型為Vehicle 。
5. 異常處理
在處理類型轉換和實體轉換時。 CAST和TREAT運算子都有關於異常處理的特定行為,
5.1.演員CAST操作員
使用CAST運算子時,如果無法進行資料轉換,則可能會發生異常。當嘗試將值轉換為由於格式或資料類型不相容而無法轉換的類型時,通常會發生這種情況。
假設Employee實體的salary值為「 5ooo 」(這不是有效整數)。當我們執行查詢將此String轉換為Integer時,資料庫會嘗試轉換此值,如果轉換失敗,則會導致JdbcSQLDataException :
Employee emp1 = new Employee();
emp1.setId(1L);
emp1.setSalary("5ooo");
em.merge(emp1);
try {
Query query = em.createQuery("SELECT CAST(e.salary AS Integer) FROM Employee e");
query.getResultList(); // This should throw an exception
fail("Expected a JdbcSQLDataException to be thrown");
} catch (PersistenceException e) {
assertTrue(e.getCause() instanceof JdbcSQLDataException,
"Expected a JdbcSQLDataException to be thrown");
}
在此測試中,我們斷言嘗試轉換無效字串值時會引發JdbcSQLDataException 。
5.2. TREAT操作員
相反, TREAT運算子處理繼承層次結構中的型別安全性向下轉換。與CAST不同, TREAT在遇到類型轉換問題時通常不會引發異常。相反,如果未找到指定子類別類型的實體,它會傳回空結果集。
假設我們在Vehicle實體中查詢Car實例,但唯一可用的車輛是Bike類型。在這種情況下,查詢不會引發異常,而是傳回空結果集:
Query query = em.createQuery("SELECT TREAT(v AS Car) FROM Vehicle v WHERE v.type = 'Bike'");
List<Car> cars = query.getResultList();
assertEquals(0, cars.size());
在此範例中,我們正在查詢Car實體,但由於不存在符合的Car實例(僅存在Bike實例),因此TREAT傳回空列表。這種方法避免了異常,並透過優雅地處理沒有實體與所需子類別類型匹配的情況,提供了一種處理類型安全轉換的乾淨方法。
6.標準API
在 Criteria API 中,不直接支援CAST 。但是,只要類型相容,我們就可以使用表達式和as()方法隱式執行類型轉換。例如,如果類型直接相容,我們可以將欄位從一種類型轉換為另一種類型。
以下是我們如何使用as()方法將表達式從一種類型轉換為另一種類型,前提是類型相容:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Integer> cq = cb.createQuery(Integer.class);
Root<Employee> employee = cq.from(Employee.class);
Expression<String> salaryExpression = employee.get("salary");
Expression<Integer> salaryAsInteger = salaryExpression.as(Integer.class);
cq.select(salaryAsInteger);
TypedQuery<Integer> query = em.createQuery(cq);
List<Integer> salaries = query.getResultList();
assertEquals(5000, salaries.get(0));
在此範例中,我們以String形式檢索salary ,並使用as()方法將其轉換為Integer 。當類型相容時,此方法有效,但 Criteria API 本身不支援直接CAST操作。
另一方面,Criteria API 支援TREAT ,並提供類型安全向下轉型的內建功能。我們可以使用TREAT透過指定子類型來處理實體繼承層次結構。此範例顯示如何將TREAT與CriteriaBuilder結合使用:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Vehicle> vehicleRoot = cq.from(Vehicle.class);
cq.select(cb.treat(vehicleRoot, Car.class))
.where(cb.equal(vehicleRoot.get("type"), "Car"));
TypedQuery<Car> query = em.createQuery(cq);
List<Car> cars = query.getResultList();
assertEquals(1, cars.size());
assertEquals(4, cars.get(0).getNumberOfDoors());
在此範例中, TREAT用於將Vehicle實體轉換為Car實例,確保類型安全並允許我們直接使用Car子類別。
七、總結
下面的表格強調了 JPA 中CAST和TREAT之間的主要區別:
| 特徵 | CAST |
TREAT |
|---|---|---|
| 目的 | 將標量值從一種類型轉換為另一種類型 | 將繼承層次結構中的實體或實體集合向下轉換為更具體的子類型 |
| 常見用法 | 主要用於基本類型轉換,例如將String轉換為Integer |
用於需要將基底類別(超類別)視為子類別的多型查詢 |
| 由JPA支持 | JPA 不直接支持 | JPA 完全支持 |
| 範圍 | 適用於基本資料類型 | 適用於繼承層次結構中的實體類型 |
八、結論
在本文中,我們探討了CAST和TREAT之間的差異。 CAST和TREAT是 JPA 中兩個不同的關鍵字,它們有不同的用途。 CAST用於在基本類型之間進行轉換,而TREAT用於將實例視為不同類型。
像往常一樣,這裡討論的程式碼可以在 GitHub 上找到。