JPA 中的 CAST 和 TREAT
1、简介
在 JPA 中,CAST 和 TREAT 是两个不同的关键字,用于操作数据类型和实体关系。本文将带你了解 CAST 和 TREAT 的区别,并通过示例来说明它们的用法。
2、JPA 中的 CAST
JPA 中的 CAST 操作符主要用于 JPQL 查询中的类型转换。它允许我们显式地将一个值从一种数据类型转换为另一种数据类型。例如,可以使用 CAST 将字符串转换为整数,反之亦然。
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 用于将一种数据类型转换为另一种数据类型,以便进行操作或比较。在执行查询时,如果需要的数据类型与数据库中存储的数据类型不同,通常会使用 CAST。
示例如下:一个名为 Employee 的实体,其 salary 字段在数据库中存储为字符串。下面是 Employee
实体的定义:
@Entity
public class Employee {
@Id
private Long id;
private String salary;
// Getter / Setter 方法省略
}
在本例中,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
。我们想从 select
Vehicle
的查询中仅检索 Car
实体。我们可以使用 TREAT 来确保类型安全:
@Entity
public class Vehicle {
@Id
private Long id;
private String type;
// Getter / Setter 方法省略
}
@Entity
public class Car extends Vehicle {
private Integer numberOfDoors;
// Getter / Setter 方法省略
}
为此,我们在 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
。尽管数据库中的底层实体是 Vehicle
类型,但结果却是一个 Car
实例列表。
5、异常处理
在处理类型转换和实体转换时,CAST 和 TREAT 运算符在异常处理方面有特定的行为。
5.1、CAST 操作符
使用 CAST 操作符时,如果数据不能转换,就会出现异常。这种情况通常发生在试图将一个值转换成一种类型时,而由于格式或数据类型不兼容,该值无法转换成这种类型。
假设 Employee
实体的 salary
值为 “5ooo”(这不是一个有效的整数,5 后面不是 0)。当我们执行查询将这个字符串转换为整数时,数据库会尝试转换这个值,如果转换失败,就会导致 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(); // 这会抛出异常
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、Criteria 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));
在本例中,我们以字符串形式获取 salary
,然后使用 as()
方法将其转换为整数。这种方法在类型兼容时有效,但 Criteria API 本身不支持直接 CAST 操作。
而,Criteria API 支持 TREAT,并为类型安全的向下转型提供了内置功能。通过指定子类型,我们可以使用 TREAT 来处理实体继承层次。下例展示了如何在 CriteriaBuilder
中使用 TREAT:
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
子类。
7、对比
下表格介绍了 JPA 中 CAST 和 TREAT 的主要区别:
特性 | CAST | TREAT |
---|---|---|
适用场景 | 将标量值从一种类型转换为另一种类型 | 将继承层次结构中的实体或实体集合向下转型为更具体的子类型 |
常见用法 | 主要用于基本类型转换,例如将字符串转换为整数 | 用于需要将基类(超类)视为子类的多态查询 |
JPA 支持 | JPA 不直接支持 | 全面支持 JPA |
范围 | 适用于基本数据类型 | 适用于继承层次结构中的实体类型 |
8、总结
本文介绍了 CAST 和 TREAT 之间的区别,CAST 和 TREAT 是 JPA 中两个不同的关键字,具有不同的用途。CAST 用于在原始类型之间进行转换,而 TREAT 则用于将实例视为不同的类型。
Ref:https://www.baeldung.com/jpa-cast-vs-treat