当前位置: 首页 > 知识库问答 >
问题:

QueryDSL谓词与JPARepository一起使用,其中字段是一个JSON字符串,使用属性转换器转换为List

易弘阔
2023-03-14

我有一个JPA实体(终端),它使用AttributeConverter将数据库字符串转换为对象列表(ProgrmRegistration)。转换器只使用JSON对象映射器将JSON字符串转换为POJO对象。

实体对象

@Entity
@Data
public class Terminal {

    @Id
    private String terminalId;

    @NotEmpty
    @Convert(converter = ProgramRegistrationConverter.class)
    private List<ProgramRegistration> programRegistrations;

    @Data
    public static class ProgramRegistration {
        private String program;
        private boolean online;
    }
}

终端使用以下JPA属性转换器来序列化对象从JSON到JSON

JPA属性转换

public class ProgramRegistrationConverter implements AttributeConverter<List<Terminal.ProgramRegistration>, String> {

    private final ObjectMapper objectMapper;
    private final CollectionType programRegistrationCollectionType;

    public ProgramRegistrationConverter() {
        this.objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        this.programRegistrationCollectionType = 
              objectMapper.getTypeFactory().constructCollectionType(List.class, Terminal.ProgramRegistration.class);
    }

    @Override
    public String convertToDatabaseColumn(List<Terminal.ProgramRegistration> attribute) {
        if (attribute == null) {
            return null;
        }
        String json = null;
        try {
            json = objectMapper.writeValueAsString(attribute);
        } catch (final JsonProcessingException e) {
            LOG.error("JSON writing error", e);
        }
        return json;
    }

    @Override
    public List<Terminal.ProgramRegistration> convertToEntityAttribute(String dbData) {
        if (dbData == null) {
            return Collections.emptyList();
        }
        List<Terminal.ProgramRegistration> list = null;
        try {
            list = objectMapper.readValue(dbData, programRegistrationCollectionType);
        } catch (final IOException e) {
            LOG.error("JSON reading error", e);
        }
        return list;
    }

}

我正在使用Spring Boot和JPARepository从数据库中获取一页终端结果。为了过滤结果,我使用布尔表达式作为谓词。对于实体上的所有过滤器值,它都工作得很好,但从JSON字符串转换的对象列表不允许我轻松编写一个表达式来过滤列表中的对象。

尝试使用QueryDSL筛选实体对象的REST API

@GetMapping(path = "/filtered/page", produces = MediaType.APPLICATION_JSON_VALUE)
public Page<Terminal> findFilteredWithPage(
    @RequestParam(required = false) String terminalId,
    @RequestParam(required = false) String programName,
    @PageableDefault(size = 20) @SortDefault.SortDefaults({ @SortDefault(sort = "terminalId") }) Pageable p) {

    BooleanBuilder builder = new BooleanBuilder();
    if (StringUtils.isNotBlank(terminalId))
       builder.and(QTerminal.terminal.terminalId.upper()
                  .contains(StringUtils.upperCase(terminalId)));

    // TODO: Figure out how to use QueryDsl to get the converted List as a predicate
    // The code below to find the programRegistrations does not allow a call to any(), 
    // expects a CollectionExpression or a SubqueryExpression for calls to eqAny() or in()
    
    if (StringUtils.isNotBlank(program)) 
       builder.and(QTerminal.terminal.programRegistrations.any().name()
                  .contains(StringUtils.upperCase(programName)));

    return terminalRepository.findAll(builder.getValue(), p);
}

我想得到任何具有程序名称等于传递到REST服务的参数的程序注册对象的终端。

我一直试图让CollectionExpression或SubQueryExpression正常工作,但没有成功,因为它们似乎都想在两个实体对象之间执行连接。我不知道如何创建路径和查询,以便它可以遍历programRegistrations,检查“program”字段是否匹配。我没有要加入的QProgamRegistration对象,因为它只是一个POJO列表。

如何让谓词仅匹配具有我正在搜索的名称的程序的终端?

这是不工作的线路:

builder.and(QTerminal.terminal.programRegistrations.any()。name()。包含(StringUtils.upper案例(编程名称)));

共有3个答案

仇龙光
2023-03-14

要得到一个简短的答案:在带有@QueryType的属性谓词中使用的参数必须在String类型的另一个属性谓词中使用。

在这篇文章中描述了一个众所周知的问题:https://github.com/querydsl/querydsl/issues/2652

我只是想分享我对这个bug的经验。

我有一个实体就像

@Entity
public class JobLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    @QueryType(PropertyType.STRING)
    private LocalizedString message;
}

我想对消息执行一些谓词。遗憾的是,使用此配置,我无法执行以下操作:

predicates.and(jobLog.message.likeIgnoreCase(escapedTextFilter));

因为我和所有人都有同样的问题!

但我找到了一种解决方法:)

predicates.and(
(jobLog.id.likeIgnoreCase(escapedTextFilter).and(jobLog.id.isNull()))
    .or(jobLog.message.likeIgnoreCase(escapedTextFilter)));
  • 重要的是,两个谓词中的escapedTextFilter必须相同
  • 实际上,在本例中,常量是第一个谓词(字符串类型)中转换为SQL的常量。在第二个谓词中,我们使用上下文值

添加性能溢出,因为我们有或在谓词中希望这可以帮助某人:)

相洛华
2023-03-14

尽管以下所需的QueryDsl看起来应该可以工作

QTerminal.terminal.programRegistrations.any().name().contains(programName);

实际上,JPA永远无法将其转换为SQL方面有意义的内容。JPA只能将其转换为以下SQL:

SELECT t.terminal_id FROM terminal t where t.terminal_id LIKE '%00%' and t.program_registrations like '%"program":"MY_PROGRAM_NAME"%'; 

这在这个用例中会起作用,但在语义上是错误的,因此它不应该起作用是正确的。尝试使用结构化查询语言选择非结构化数据毫无意义

唯一的解决方案是将数据视为DB搜索条件的字符,并在查询完成后将其视为对象列表,然后在Java中执行行过滤。尽管这使得分页功能非常无用。

一种可能的解决方案是为用于DB搜索条件的列提供一个辅助只读字符串版本,而不是由AttributeConverter转换为JSON。

@JsonIgnore
@Column(name = "programRegistrations", insertable = false, updatable = false)
private String programRegistrationsStr;

真正的解决方案是当您希望对非结构化数据进行结构化查询时不要使用非结构化数据因此将数据转换为本地支持JSON查询的数据库或在DDL中正确建模数据。

上官羽
2023-03-14

AttributeConverter在Querydsl中有问题,因为它们在JPQL(JPA的查询语言)本身中有问题。目前尚不清楚属性的底层查询类型是什么,以及参数是应该是该查询类型的基本类型,还是应该使用转换进行转换。这种转换虽然看起来合乎逻辑,但在JPA规范中没有定义。因此,需要使用查询类型的基本类型,这导致了新的困难,因为Querydsl无法知道它需要的类型。它只知道属性的Java类型。

一种解决方法是,通过使用QueryType(PropertyType.STRING)对字段进行注释,强制该字段生成字符串路径。虽然这解决了某些查询的问题,但在其他场景中,您将遇到不同的问题。有关更多信息,请参阅此线程。

 类似资料:
  • 问题内容: 我对Python中的JSON感到有些困惑。在我看来,这就像是一本字典,出于这个原因,我正在尝试这样做: 但是当我这样做时,它会给出一个错误。 如何将该字符串转换为结构,然后调用以获得“示例词汇表”? 问题答案:

  • 我只是无法在c中转换不同的数据类型,我知道c是一种强类型语言,所以我在这里使用了,但我面临一个问题,错误消息是 从“std::string{aka std::basic_string}类型转换为“int”类型的static_

  • 问题内容: 我正在使用。我想转换为Json 。 没用 为什么? 问题答案: 试试这个:

  • 我正在尝试从json转换为Java对象。我收到一个json字符串,其中包括一个书籍列表: 我创建了一个名为Books的对象: 另外,我正在使用doGet方法从RESTful webservice中以以下方式检索这些数据: 干杯

  • 我有一个RestController类,它有一个方法可以根据电影的标题搜索电影: 如果我发送一个json字符串 从邮递员到endpoint,我收到一个空白的回复。 然后我做了一个 就在进入方法后找到字符串filmSearch正是我从邮递员发送的JSON字符串。我的应用程序没有看到JSON和提取值从filmSearch在我的请求附加到应用程序内字符串filmSearch. 如果我移除 在请求映射的一

  • 正如在这里被问及的,这个#58538732的后续问题 根据Lukas Eder的建议,我编写了一个,将转换为 现在看起来如下所示 出现以下错误消息: 线程“main”java.lang中出现异常。错误:未解决的编译问题: 无法访问QueryFeaturesTask类型的封闭实例。必须使用QueryFeaturesTask类型的封闭实例限定分配(例如,x.new A(),其中x是QueryFeatu