【问题标题】:how to treat mysql json column with queryDSL?如何用queryDSL处理mysql json列?
【发布时间】:2021-10-08 08:49:01
【问题描述】:

我想知道如何处理mysql json 列。

product table:
...
    category_ids                     json                         null,
...

category_ids ex) [19, 102, 108]

如果我想搜索包含类别 id 102 的产品列表 如何使用 queryDSL 进行查询?

我尝试使用 JsonNode 类型,但它不起作用。

【问题讨论】:

    标签: hibernate jpa querydsl


    【解决方案1】:

    根据标签,我假设您使用querydsl-jpa 创建 JPQL(或 Hibernates HQL)查询。需要注意这一点,因为为了让 JSON 类型与 Querydsl 一起使用,它们不仅必须与 Querydsl 和 MySQL 一起使用,而且最重要的是与查询语言本身一起使用,在这种情况下来自 Hibernate。

    我假设您已经设法将您的 json 列正确映射到 Jacksons JsonNode 类型。如果不是,那么需要注意的是,对于任何非标准类型,自定义类型都需要在 Hibernate 中注册。您可以制作自己的自定义类型,也可以使用现有的实现。我强烈推荐由 Vlad 开发的 hibernate-types 库(包括我自己在内的其他一些人的贡献)。如何将hibernate-types 集成到您的项目中在他对这个 Stackoverflow 问题的回答中得到了很好的解释:https://stackoverflow.com/a/37946530/2104280

    你需要做的第一件事是在你的项目pom.xml配置文件中设置如下Hibernate TypesMaven依赖:

       <dependency>
           <groupId>com.vladmihalcea</groupId>
           <artifactId>hibernate-types-52</artifactId>
           <version>${hibernate-types.version}</version>
       </dependency>
    

    现在,您需要在类级别或 package-info.java 包级别描述符中声明 JsonType,如下所示:

       @TypeDef(name = "json", typeClass = JsonType.class)
    

    而且,实体映射将如下所示:

       @Type(type = "json")
       @Column(columnDefinition = "jsonb")
       private Location location;
    

    现在您将能够在查询中的简单操作中使用 JSON 属性。允许的操作基本上包括基本比较(=!=IS (NOT) NULL)。其他操作不可用,因为这些仅在 MySQL 中定义,而不是在 JPQL 或 HQL 查询语言中。

    您想检查 JSON 数组是否包含特定元素。我假设 category_ids 代表一个 JSON 数组。那么这意味着您想要生成的 MySQL sn-p 可能类似于category_ids ? :categoryId

    我想指出的是,这仅适用于存储为字符串的 ID,而不是 JSON 数组中的数字。您可能希望将category_ids 映射为bigint[]。与 JSON 类型一样,Hibernate-Types 库也支持数组类型,因此答案对数组同样适用。

    但是,JSON_CONTAINS(category_ids, :categoryId) 不是有效的 HQL/JPQL,因为该查询语言中不存在该函数。如果我们想在 HQL/JPQL 中渲染 JSON_CONTAINS(category_ids, :categoryId),我们需要定义一个自定义函数,将 JSON_CONTAINS(category_ids, :categoryId) 渲染为 SQL。

    在 Hibernate 中声明自定义函数有两种方式。

    1. 扩展您正在使用的方言并在那里声明函数。这种方法在https://stackoverflow.com/a/42486291/2104280 中有很好的描述。
    2. 在引导期间使用MetadataBuilderInitializer SPI 注册自定义函数。这种方法在https://stackoverflow.com/a/41369853/2104280 中有所描述。

    使用MetadataBuilderInitializer 方法,代码最终将如下所示:

    public class JSONMetadataBuilderInitializer
            implements MetadataBuilderInitializer {
        @Override
        public void contribute(MetadataBuilder metadataBuilder,
                StandardServiceRegistry serviceRegistry) {
            metadataBuilder.applySqlFunction("json_contains_key",
                new SQLFunctionTemplate(BooleanType.INSTANCE,
                    "JSON_CONTAINS(?1, ?2, '$'"));
        }
    }
    

    最后,您的JsonNode 属性得到了一个Hibernate 类型,以及一个用于检查键是否存在的自定义函数。在 JPQL/HQL 中可以按如下方式使用密钥:

    SELECT product FROM Product product WHERE json_contains_key(product.categories, '1234') = true;
    

    这将产生以下 SQL:

    SELECT * FROM product WHERE JSON_CONTAINS(categories, '1234', '$') = true;
    

    但是,您想通过 Querydsl 构造这个 JPQL/HQL 查询,这意味着还需要几个步骤。我们需要为创建的函数构造新的表达式类型。基本上有两种选择:

    1. 创建一个新的Operator 实现,并在扩展的JPQLTemplates 中为该Operator 注册一个Template
    2. 创建一个TemplateExpression(字面意思是JPQL 的一个片段)。

    第二种方法更简单,我将在这里使用。使用TemplateExpression,您最终会在 Querydsl 中使用以下代码来生成上述 JPQL 查询:

    queryFactory.select(QProduct.product)
        .from(QProduct.product)
        .where(Expressions.booleanTemplate(
            "json_contains_key({0}, {1})",
            QProduct.product.categories,
            Expressions.constants("1234").isTrue())
        .fetch();
    

    但是,这并不像您在 Querydsl 中习惯的那样流畅。可以使用自定义表达式类型扩展 Querydsl,因此假设引入了 JsonExpression。还可以扩展querydsl-apt 以在静态元模型(Q 类)中自动生成此表达式类型的路径。我专门为此目的构建了扩展库hibernate-types-querydsl-apt。它为来自Hibernate-Types 项目的自定义类型(例如范围、数组、json、hstore、间隔)添加了各种自定义表达式类型。它还自动为主要操作注册自定义函数。使用我的扩展,数组的用例将很简单:

    @Entity
    @TypeDefs({
            @TypeDef(name = "int-array", typeClass = IntArrayType.class, defaultForType = int[].class)
    })
    public class ArrayEntity {
        @Id
        Long id;
    
        @Type(type = "int-array")
        @Column(name = "sensor_values", columnDefinition = "integer[]")
        int[] sensorValues;
    }
    
    List<Tuple> fetch = new JPAQuery<>(entityManager, ExtendedHQLTemplates.DEFAULT)
        .from(arrayEntity)
        .select(arrayEntity)
        .where(arrayEntity.sensorValues.contains(123))
        .fetch();
    

    更多示例请访问:https://github.com/jwgmeligmeyling/hibernate-types-querydsl-apt/blob/master/querydsl-ext-testsuite/src/test/java/com/pallasathenagroup/querydsl/ArrayEntityPathTest.java

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-23
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-31
      • 2017-03-14
      相关资源
      最近更新 更多