【问题标题】:Query resulting into "ERROR: operator does not exist: character varying ~~ bytea"查询结果为“错误:运算符不存在:字符变化 ~~ bytea”
【发布时间】:2021-01-06 07:11:09
【问题描述】:

我有一个查询,我需要先检查输入参数是否为空或比较列值以传递输入参数。表示列值可以为null或通过指定条件(?3为null或cd.name like %?3%)

public interface PageableCategoryRepository extends PagingAndSortingRepository<Category, Long> {
  @Query(
      value = "select distinct c from Category c left join fetch c.descriptions cd join fetch cd.language cdl join fetch c.merchantStore cm"
          + "  where cm.id=?1 and cdl.id=?2 and (?3 is null or cd.name like %?3%) order by c.lineage, c.sortOrder asc",
      countQuery = "select  count(c) from Category c join c.descriptions cd join c.merchantStore cm "
          + "where cm.id=?1 and cd.language.id=?2 and (?3 is null or cd.name like %?3%)")
  Page<Category> listByStore(Integer storeId, Integer languageId, String name, Pageable pageable);
}

对于 name 属性中传递的空值,上述查询失败。 错误:

错误:运算符不存在:字符变化 ~~ bytea 提示:没有运算符匹配给定的名称和参数类型。您可能需要添加显式类型转换。 位置:3259

我尝试在 Google 和 Stack Overflow 上搜索。有许多类似的问题被问和回答。但这些解决方案都不适合我。

如果有人能提供一些见解或方向,将不胜感激。

注意:Spring boot 版本 - 2.2.7.RELEASE,使用的 Postgresql 库版本 - 42.2.16,使用的 Postgresql 版本 - 12.4

【问题讨论】:

  • 很确定问题出在cd.name like %?3%,因为LIKE 解析为~~。所以发生错误是因为cd.name 我假设是一个varchar 正在通过~~ 与Postgres 认为是bytea 类型的东西进行比较。换句话说,当String name 为空时,正在对%?3% 执行某些操作以使其显示为bytea
  • name 是否等于 null?
  • 传递给过滤结果的名称可能为空。
  • 但是不为null的时候是否有效?
  • 是的,它确实有效。

标签: java postgresql spring-boot nativequery


【解决方案1】:

如果是null,Postgres 无法判断参数的类型。

这里已经讨论过这个问题:Spring Data Rest: "Date is null" query throws an postgres exception

建议的解决方案是显式转换参数(如错误消息中的建议),或将参数包装在 coalesce 语句中。 所以这应该是诀窍: 替换所有这些:

?3 is null 

通过这种说法:

coalesce(?3, null) is null

当涉及到参数变化的查询时,查看 Criteria API 而不是使用@Query 也是一个好主意,因为它允许非常动态地创建查询: https://www.baeldung.com/hibernate-criteria-queries

【讨论】:

  • 我试过这个解决方案。但仍然是同样的错误。因为问题在于条件的后面部分,而不是空检查。
【解决方案2】:

最简单的解决方案是使用显式类型转换。而且LIKE的右边参数必须是字符串,所以用单引号括起来:

WHERE ... (?3::text IS NULL
           OR cd.name::text LIKE '%' || ?3::text || '%'
          )

【讨论】:

    【解决方案3】:

    似乎使用命名参数而不是匿名参数可以使其工作。

    就我而言,这是行不通的:

    @Query("""
        SELECT p FROM Participant p
        WHERE (?1 IS NULL OR p.firstName LIKE ?1)
        AND ?2 IS NULL OR e.id = ?2
        AND p.waitingList = ?3
        """)
    List<Participant> findFiltered(String searchCriteria, Long eventId, boolean waitingList);
    

    2021-07-05 10:13:39.768 警告 28896 --- [XNIO-1 任务 3] o.h.engine.jdbc.spi.SqlExceptionHelper:SQL 错误:0,SQLState: 42883 2021-07-05 10:13:39.768 错误 28896 --- [XNIO-1 任务 3] o.h.engine.jdbc.spi.SqlExceptionHelper:错误:运算符不存在:文本 ~~ bytea 提示:没有运算符与给定名称和参数类型匹配。您可能需要添加显式类型转换。位置:951

    但是使用命名参数,它可以工作:

    @Query("""
        SELECT p FROM Participant p
        WHERE (:searchCriteria IS NULL OR p.firstName LIKE :searchCriteria)
        AND :eventId IS NULL OR e.id = :eventId
        AND p.waitingList = :waitingList
        """)
    List<Participant> findFiltered(@Param("searchCriteria") String searchCriteria, @Param("eventId") Long eventId, @Param("waitingList") boolean waitingList);
    

    否则,如错误消息所述,显式转换也可以正常工作:

    @Query("""
        SELECT p FROM Participant p
        WHERE (cast(?1 as text) IS NULL OR p.firstName LIKE cast(?1 as text))
        AND cast(?2 as long) IS NULL OR e.id = cast(?2 as long)
        AND p.waitingList = ?3
        """)
    List<Participant> findFiltered(String searchCriteria, Long eventId, boolean waitingList);
    

    有关可用的转换类型,请参阅https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#basic-provided

    我使用 PostgreSQL 13。

    【讨论】:

      【解决方案4】:

      如果您有可能在本机查询中使用空值,那么您必须直接使用 JPA 接口,而不是让 Spring 为您调用它们。而不是:

        @Query(
            value = "select distinct c from Category c left join fetch c.descriptions cd join fetch cd.language cdl join fetch c.merchantStore cm"
                + "  where cm.id=?1 and cdl.id=?2 and (?3 is null or cd.name like %?3%) order by c.lineage, c.sortOrder asc",
            countQuery = "select  count(c) from Category c join c.descriptions cd join c.merchantStore cm "
                + "where cm.id=?1 and cd.language.id=?2 and (?3 is null or cd.name like %?3%)")
        Page<Category> listByStore(Integer storeId, Integer languageId, String name, Pageable pageable);
      

      你需要:

      Page<Category> listByStore(Integer storeId, Integer languageId, String name, Pageable pageable) {
          EntityManager em = ...get from somewhere (maybe parameter?);
          TypedQuery<Category> q = (TypedQuery<Category>) em.createNativeQuery(..., Category.class);
          Integer exampleInt = 0;
          String exampleString = "";
          q.setParameter(1, exampleInt).setParameter(1, storeId);
          q.setParameter(2, exampleInt).setParameter(2, languageId);
          q.setParameter(3, exampleString).setParameter(3, name);
      }
      

      第一次调用setParameter 告诉它类型,第二次设置实际值。

      这背后的原因是 Postgres 在解析时确定类型,而 Hibernate 无法确定 null 的类型,因此在某一阶段假定为 java.io.Serializable,然后告诉它假定 @987654326 @在稍后阶段。这样做是出于与其他数据库的遗留兼容性原因,并且不太可能更改。也许新的 Hibernate 6.0 类型系统会解决这个问题,但我没有跟上。那么当它告诉 Postgres 类型是 bytea 时,查询解析器找不到在 bytea 和给定的其他数据库类型之间注册的隐式类型转换器,所以它会抛出一个错误。

      【讨论】:

      • 你的比喻很有意思。但命名查询不接受查询中的“%”。因此需要在 setParameter 中提供“%”+“name”+“%”,这将违背编写查询的唯一目的,即如果 name 参数为 null,则返回所有结果。
      • 你有三个选择。这个,或者在调用listByStore之前将参数替换为空字符串,或者将解决方法的补丁发送到Postgres,Hibernate或SpringBoot。正如我在根问题中所建议的那样,作为个人偏好,我会使用已过滤输入的正则表达式进行本机查询。
      猜你喜欢
      • 2012-05-19
      • 1970-01-01
      • 2018-02-17
      • 1970-01-01
      • 2020-08-24
      • 2014-04-05
      • 2016-06-06
      • 1970-01-01
      相关资源
      最近更新 更多