我正在使用 postgresql 和 spring boot,并且我有一个像这样定义的 json 列
ALTER TABLE core."foo"
ADD COLUMN if not exists config json;
Foo
实体的映射如下
import com.vladmihalcea.hibernate.type.json.JsonType;
import org.hibernate.annotations.Type;
...
@Type(JsonType.class)
private Config config;
Foo
实体包含很多列,因此我使用自定义 dto 来执行一些优化的选择
@Query(
"""
SELECT new path.to.package.FooProjection(
f.id,
f.config
)
FROM Foo f
WHERE ...
""")
Optional<FooProjection> fetchMinimalFoo(Long fooId);
FooProjection
是经典的 pojo,config
字段映射如下
@Getter
@Builder
@RequiredArgsConstructor
public class FooProjection {
private final Long id;
private final Config config;
问题是执行查询时出现此错误
java.lang.IllegalStateException: Cannot instantiate class 'path.to.package.FooProjection'
(it has no constructor with signature [java.lang.Long, java.lang.Object], and not every argument has an alias)
它将 json 字段视为一个对象!!
在阅读了 Thorben Janssen 的“实体或 DTO – 何时应该使用哪个投影?”和“为什么、何时以及如何在 JPA 和 Hibernate 中使用 DTO 投影”后,很明显 Hibernate 在DTO 投影,并且构造函数必须与查询返回的确切类型匹配。
由于 Hibernate 将 JSON 字段视为通用
Object
,或者可能是其他默认类型,如 String
,具体取决于您的 Hibernate 方言,因此您需要确保您的 FooProjection
构造函数可以处理此问题。
更新
FooProjection
以接受 JSON 字段作为 Hibernate 将其转换为的类型。String
(常见情况),您会看到类似:
@Getter
@Builder
@RequiredArgsConstructor
public class FooProjection {
private final Long id;
private final String configJson; // Changed to String assuming JSON is returned as a String
// Constructor matching the query projection
public FooProjection(Long id, String configJson) {
this.id = id;
this.config = Config.createFromJson(configJson); // Assuming you have a method to handle JSON parsing
}
}
如果 Hibernate 没有自然地将 JSON 转换为字符串,您可能需要在查询中显式转换或转换它。这取决于您的特定数据库方言。对于 PostgreSQL,您可以修改查询以将 JSON 转换为文本:
@Query(
"""
SELECT new path.to.package.FooProjection(
f.id,
f.config::jsonb#>>'{}' // Casts to JSONB and fetches the whole JSON object as text
)
FROM Foo f
WHERE f.id = :fooId
""")
Optional<FooProjection> fetchMinimalFoo(@Param("fooId") Long fooId);
假设
config
可以完全表示为字符串。
如果
config
#>>
运算符中的路径。确保您的 Config
类或相关实用程序中有一个方法可以将 JSON 字符串转换回您的
Config
对象。JSON 到
Config
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
public class JsonUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static Config createFromJson(String json) {
try {
return objectMapper.readValue(json, Config.class);
} catch (JsonProcessingException e) {
// Log the error or throw a custom exception as per your error handling policy
System.err.println("Error parsing JSON to Config: " + e.getMessage());
return null; // or throw, depending on how you want to handle parse errors
}
}
}
FooProjection
DTO 必须正确使用此方法:如果
config
中的 FooProjection
字段是 Config
类型,则需要修改构造函数来解析 JSON 字符串:@Getter
@Builder
@RequiredArgsConstructor
public class FooProjection {
private final Long id;
private Config config; // Not final, to allow setting after parsing
public FooProjection(Long id, String configJson) {
this.id = id;
this.config = JsonUtil.createFromJson(configJson); // Convert JSON string to Config
}
}