解决异常 PSQLException: Operator Does Not Exist: Character Varying = UUID

1、简介

本文将带你了解 JPA 与 PostgreSQL 交互时出现 PSQLException 异常:“Operator Does Not Exist: Character Varying = UUID” 的原因,以及如何处理和避免该异常。

2、异常原因

PostgreSQL 区分 Character Varying(字符串)和 UUID 数据类型。这种区分要求在这些类型之间进行比较时进行 显式类型转换。因此,当我们尝试直接将 UUID 值与字符串(VARCHAR)列进行比较时,PostgreSQL 会抛出异常,因为它缺乏用于这种特定类型比较的操作符。

举个例子,有一个 User 实体,其 varchar 列为 uuid

@Entity
@Table(name = "User_Tbl")
public class User{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(columnDefinition = "varchar")
    private UUID uuid;

    // Gette / Setter 方法省略
}

当尝试使用 UUID 值查询数据库时,会出现异常:

// UUID
UUID testId = UUID.fromString("c3917b5b-18ed-4a84-a6f7-6be7a8c21d66");
User user = new User();
user.setUuid(testId);
user.setName("John Doeee");
userRepository.save(user);

Throwable throwable = assertThrows(InvalidDataAccessResourceUsageException.class,
  () -> userRepository.findByUuid(testId),
  "Expected ERROR: operator does not exist: character varying = uuid");
assertTrue(getRootCause(throwable) instanceof PSQLException);

3、修复异常

要解决这个问题,需要确保正确处理 Character Varying(字符串)和 UUID 值之间的比较。

3.1、使用 CAST() 函数

可以使用 PostgreSQL 中的 CAST() 函数,在 JPA 查询中明确地将 UUID 值转换为字符串。

CAST() 函数是 PostgreSQL 的内置函数,允许将一种数据类型的值转换为另一种数据类型。这可以确保 PostgreSQL 正确处理 UUID 值与 varchar 类型之间的比较。

使用示例如下:

@Query("SELECT u FROM User u WHERE u.uuid = CAST(:uuid AS text)")
Optional<User> findByUuidWithCastFunction(@Param("uuid") UUID uuid);

通过将字符串值转换为 UUID,可以确保数据库能正确执行比较:

UUID testId = UUID.fromString("c3917b5b-18ed-4a84-a6f7-6be7a8c21d66");

Optional<User> userOptional = userRepository.findByUuidWithCastFunction(testId);
assertThat(userOptional.isPresent(), is(true));
assertThat(userOptional.get().getUuid(), equalTo(testId));

生成的 SQL 查询如下,包含了 CAST(:id AS text) 操作:

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.name as name2_0_,
        user0_.uuid as uuid3_0_ 
    from
        user_tbl user0_ 
    where
        user0_.uuid=cast(? as text)

cast(? as text) 显示了如何使用 CAST() 函数将 UUID 参数转换为 text 类型,以确保与 PostgreSQL 中的 varchar 列兼容。

3.2、使用自定义转换器(Converter)

除了使用 SQL 函数直接转换外,还可以使用 JPA 中的 @Converter 注解为 UUID 对象和 varchar 列定义一个自定义转换器。该转换器用于 UUID 对象与其在数据库中的字符串表示之间的无缝转换。

首先,实现 UUIDConverter 类,该类实现了 AttributeConverter<UUID, String>

@Converter
public class UUIDConverter implements AttributeConverter<UUID, String> {
    @Override
    public String convertToDatabaseColumn(UUID uuid) {
        return uuid.toString();
    }

    @Override
    public UUID convertToEntityAttribute(String dbData) {
        return UUID.fromString(dbData);
    }
}

接下来,可以在 JPA 实体中的 UUID 字段上使用 @Convert 注解:

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

    @Convert(converter = UUIDConverter.class)
    @Column(columnDefinition = "varchar")
    private UUID uuid;

    // Getter/ Setter 方法省略
}

@Convert 注解配置 JPA 在将实体持久化到数据库时自动将 UUID 字段转换为字符串 (VARCHAR) 类型,而在检索时则反之:

Optional<UserWithConverter> userOptional = userWithConverterRepository.findByUuid(testId);
assertThat(userOptional.isPresent(), is(true));
assertThat(userOptional.get().getUuid(), equalTo(testId));

4、总结

本文介绍了在使用 JPA 和 Postgres 中出现异常 PSQLException “operator does not exist: character varying = uuid” 的原因,以及解决办法。


Ref:https://www.baeldung.com/java-psqlexception-operator-does-not-exist-character-varying-uuid