Spring Boot SQL 查询的常见陷阱:处理 PostgreSQL 中的类型不匹配
作为开发人员,我们都遇到过似乎不知从何而来的神秘错误消息。一分钟,我们的 Spring Boot应用程序 运行顺利;接下来,我们将关注有关不兼容数据类型的错误。 😅 这既令人沮丧又令人困惑,尤其是在处理复杂的查询设置时。
最近,我在 Spring Boot 中遇到了 PostgreSQL 错误: “运算符不存在:字符变化=smallint。” 尝试使用时出现此消息 枚举集 在 SQL 查询的 IN 子句中。枚举类型和数据库列类型之间的不匹配在看似简单的代码中造成了意外的问题。
虽然人们很容易将其归咎于数据库怪癖或 Spring Boot,但真正的问题通常在于枚举和数据库类型的映射方式。 Java 枚举在映射到数据库时需要特殊处理,尤其是对于 PostgreSQL。了解这些详细信息可以节省时间并防止将来在 Spring Boot 中使用枚举时出现问题。
在本指南中,我将解释如何识别问题并逐步实施实际的解决方案。从我自己的调试之旅到特定代码修复,您将获得避免查询中类型不匹配并确保无缝数据库交互所需的工具。 🔧
命令 | 问题上下文中的使用说明 |
---|---|
@Enumerated(EnumType.STRING) | 此注释可确保枚举值(例如 AccountType)作为字符串而不是序数值存储在数据库中。使用 EnumType.STRING 对于数据库中的可读和可管理值至关重要,特别是对于涉及枚举过滤的 SQL 查询。 |
CriteriaBuilder | CriteriaBuilder 是 JPA Criteria API 的一部分,用于以类型安全的方式创建动态查询。在这里,它有助于根据枚举的字符串值构建条件查询,最大限度地减少 SQL 注入风险并避免直接本机查询问题。 |
cb.equal() | CriteriaBuilder 中的方法,用于创建列与特定值匹配的条件。在这种情况下,它将枚举转换为字符串后将 userCode 与每个 AccountType 值进行匹配,从而避免与 PostgreSQL 发生类型不匹配错误。 |
@Query | 此注释允许直接在 Spring Data JPA 存储库中定义自定义 SQL 查询。在这里,它包括一个带有 IN 子句的本机查询,该 IN 子句使用参数化枚举值,专门用于适应 PostgreSQL 对本机查询中数据类型的处理。 |
cb.or() | 此 CriteriaBuilder 方法在多个 Predicate 对象之间构造逻辑 OR 运算。此处使用它允许在单个查询中使用多个 AccountType 值,从而增强按多种类型过滤结果时的灵活性。 |
entityManager.createQuery() | 执行使用 CriteriaBuilder API 创建的动态构造的查询。它允许我们通过 JPA 管理复杂的 SQL 操作,执行枚举过滤器查询,而无需在 PostgreSQL 中进行显式类型转换。 |
@Param | 与 @Query 注释一起使用,将方法参数映射到 SQL 中的命名参数。这可确保 accountTypes 集中的枚举值正确传递到查询,从而有助于提高可读性和易于维护。 |
.stream().map(Enum::name).collect(Collectors.toList()) | 此流处理行将 AccountType 中的每个枚举转换为其字符串名称。这对于与 SQL 兼容至关重要,因为 PostgreSQL 无法直接在本机查询中解释枚举,从而确保类型一致性。 |
Optional<List<SystemAccounts>> | 返回一个包装的结果列表,确保 findAll 查询可以正常处理空结果。这避免了空检查并鼓励更干净、无错误的代码。 |
assertNotNull(results) | 验证查询结果不为空的 JUnit 断言,确认数据库交互成功并且 SQL 查询按预期运行。这是在单元测试中验证解决方案正确性的关键。 |
使用 PostgreSQL 解决 Spring Boot 中的数据类型不匹配问题
当与 春季启动 和 PostgreSQL,开发人员经常遇到类型不匹配的问题,尤其是枚举。在这种情况下,会出现错误“operator does not exit:charactervarying=smallint”,因为 PostgreSQL 无法在本机查询中直接将 Java 枚举解释为 SQL 类型。这里,SystemAccounts 实体包括由 AccountType 枚举表示的 userCode 字段,该字段映射 Java 中的“PERSONAL”或“CORPORATE”等值。但是,当尝试使用一组枚举进行本机 SQL 查询时,PostgreSQL 无法自动匹配枚举类型,从而导致此错误。为了克服这个问题,在将枚举传递给查询之前将其转换为字符串至关重要。 🎯
在提供的解决方案中,我们首先使用 @Enumerated(EnumType.STRING) 注释调整 SystemAccounts 中的枚举映射。这指示 JPA 将每个 AccountType 存储为可读字符串而不是数字序数。这是一个很小的变化,但它通过避免数值来简化未来的数据处理,数值会使数据库中的调试变得复杂。在我们的存储库中,我们可以使用自定义 @Query 注释来指定 SQL 逻辑。但是,由于 PostgreSQL 在查询中仍然需要枚举作为字符串,因此我们需要在传入之前将 AccountType 值处理为字符串格式。
CriteriaBuilder API 为这个问题提供了动态解决方案。使用 CriteriaBuilder,我们可以通过在 Java 中以编程方式构建查询来避免原生 SQL。这种方法使我们能够添加枚举过滤器,而无需手动编写 SQL,从而减少 SQL 错误并有助于可维护性。在我们的脚本中,我们根据每个枚举的字符串值创建一个谓词条件列表,使用 cb.equal() 来匹配 Set 中的每个 AccountType。然后,cb.or() 组合这些谓词,允许在同一查询中使用多个值。这种灵活的设置动态管理枚举到字符串的转换,最大限度地减少与 PostgreSQL 的兼容性问题。
最后,该解决方案结合了单元测试来验证兼容性。使用 JUnit,我们确认每个 AccountType 都适用于我们的查询,验证 userCode 字段是否可以存储“个人”或“公司”值并毫无错误地检索它们。此测试方法首先设置所需的 AccountType 值并运行 findAllByUserCodes() 查询来检查结果。添加assertNotNull()和assertTrue()检查可以保证我们不会遇到空值或不正确的值,从而确保我们的解决方案有效地处理所有情况。通过此设置,应用程序可以更好地处理生产中各种条件下的枚举查询。 🧪
使用 PostgreSQL 枚举解决 Spring Boot 中的类型不匹配错误
解决方案 1:Spring Boot 后端 - 重构 PostgreSQL 中的查询和枚举处理
// Problem: PostgreSQL expects specific data types in queries.
// Solution: Convert enums to strings for query compatibility with PostgreSQL.
// This Spring Boot backend solution is clear, optimized, and includes type checks.
@Entity
@Table(name = "system_accounts")
@Getter
@Setter
public class SystemAccounts {
@Id
@Column(name = "id", nullable = false)
private UUID id;
@Column(name = "user_code")
private String userCode; // Store as String to avoid type mismatch
}
// Enumeration for AccountType
public enum AccountType {
PERSONAL,
CORPORATE
}
// Repository Query with Enum Handling
@Query(value = """
SELECT sa.id FROM system_accounts sa
WHERE sa.user_code IN :accountTypes""", nativeQuery = true)
Optional<List<SystemAccounts>> findAllByUserCodes(@Param("accountTypes") List<String> accountTypes);
// This query accepts a List of strings to avoid casting issues.
// Convert AccountType enums to Strings in Service
List<String> accountTypeStrings = accountTypes.stream()
.map(Enum::name)
.collect(Collectors.toList());
替代方法:使用 JPA Criteria API 进行灵活的类型处理
解决方案 2:使用 JPA CriteriaBuilder 的后端实现强大的枚举处理
// Using CriteriaBuilder to dynamically handle enums in queries with automatic type checking.
// This approach uses Java’s Criteria API to avoid type mismatches directly in the code.
public List<SystemAccounts> findAllByUserCodes(Set<AccountType> accountTypes) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<SystemAccounts> query = cb.createQuery(SystemAccounts.class);
Root<SystemAccounts> root = query.from(SystemAccounts.class);
Path<String> userCodePath = root.get("userCode");
List<Predicate> predicates = new ArrayList<>();
// Add predicates for enum values, converting to String for matching
for (AccountType type : accountTypes) {
predicates.add(cb.equal(userCodePath, type.name()));
}
query.select(root)
.where(cb.or(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}
测试解决方案:通过单元测试验证兼容性
用于验证类型处理的 JUnit 测试脚本
// This JUnit test ensures both solutions handle enums correctly with PostgreSQL.
// Tests database retrieval for both AccountType values: PERSONAL and CORPORATE.
@SpringBootTest
public class SystemAccountsRepositoryTest {
@Autowired
private SystemAccountsRepository repository;
@Test
public void testFindAllByUserCodes() {
Set<AccountType> accountTypes = Set.of(AccountType.PERSONAL, AccountType.CORPORATE);
List<SystemAccounts> results = repository.findAllByUserCodes(accountTypes);
// Verify results are returned and types match
assertNotNull(results);
assertTrue(results.size() > 0);
results.forEach(account ->
assertTrue(account.getUserCode().equals("PERSONAL") || account.getUserCode().equals("CORPORATE"))
);
}
}
使用 Spring Boot 在 PostgreSQL 中处理枚举到字符串的转换
使用时 春季启动 使用 PostgreSQL,在数据库查询中处理枚举通常需要特别注意,特别是当涉及到枚举时 原生 SQL 查询。默认情况下,PostgreSQL 不直接支持 Java 枚举,而是需要兼容的数据类型,例如 varchar 或者 文本 在查询中。例如,当我们需要基于像 AccountType 这样的枚举来过滤结果时,PostgreSQL 要求我们在执行查询之前将 Java 枚举转换为字符串值。此转换可防止常见的“运算符不存在”错误,该错误在数据库尝试将枚举与不兼容的类型(如smallint 或字符变化)进行比较时发生。
解决这个问题的一种方法是利用 @Enumerated(EnumType.STRING) Spring Boot 中的注释,它将枚举作为字符串值直接存储在数据库中。但是,对于涉及本机查询的场景,通常需要在查询本身中将枚举转换为字符串。通过使用类似的方法 .stream() 和 map(Enum::name),我们可以为枚举值生成一个字符串表示形式的列表,然后可以将其传递到 PostgreSQL,而不会出现任何类型不匹配问题。这种方法确保了灵活性,使我们能够无缝地按多个 AccountType 值进行过滤而不会出现错误。
对于具有更复杂枚举用法的应用程序,另一种方法是使用 CriteriaBuilder API,它允许我们以类型安全的方式动态构造查询,而无需手动编写 SQL。该 API 对于创建可重用且与数据库无关的代码特别有用,因为它会自动将枚举转换为兼容的数据库类型,从而降低类型错误的风险。通过这种方法,查询构建过程得到简化,开发人员可以灵活地以统一的方式处理各种基于枚举的过滤器。
有关在 Spring Boot 中将枚举与 PostgreSQL 结合使用的常见问题
- 为什么 PostgreSQL 会给出枚举的类型不匹配错误?
- 发生此错误是因为 PostgreSQL 需要像这样的兼容类型 varchar 对于枚举。如果枚举未显式转换为字符串,则 PostgreSQL 无法执行比较。
- 如何将枚举作为字符串存储在数据库中?
- 要将枚举存储为字符串,请使用以下注释枚举字段 @Enumerated(EnumType.STRING)。这可确保每个枚举值作为文本存储在数据库中,从而简化将来的查询操作。
- 我可以使用 CriteriaBuilder 来避免枚举的类型不匹配问题吗?
- 是的, CriteriaBuilder 是一个功能强大的工具,可让您创建动态、类型安全的查询,而无需手动类型转换,从而更轻松地处理 Spring Boot 应用程序中的枚举。
- 在本机查询之前将枚举转换为字符串有什么好处?
- 使用以下方法将枚举转换为字符串 Enum::name 使它们与 PostgreSQL 预期的文本类型兼容,避免查询执行期间出现错误。
- 当传递给 SQL 时,如何处理 Set 中的枚举转换?
- 对于集合,使用 .stream().map(Enum::name).collect(Collectors.toList()) 在将集合中的每个枚举传递给本机 SQL 查询之前将其转换为字符串。
解决 Spring Boot 中 PostgreSQL 的类型不匹配问题
在 Spring Boot 和 PostgreSQL 中使用枚举最初可能会导致错误,但解决方案很简单,只需进行一些调整。在将枚举传递到 SQL 查询之前将其转换为字符串可以防止类型冲突,并且像 @Enumerated(EnumType.STRING) 这样的注释可以简化在数据库中存储可读枚举值的过程。 🛠️
使用 CriteriaBuilder 是另一种有效的解决方案,因为它避免了本机 SQL 并动态处理枚举,从而减少了错误并创建灵活的代码。这两种方法都可以防止类型不匹配,同时允许动态查询,从而在 Spring Boot 应用程序中实现更干净、更健壮的后端设置。 🚀
Spring Boot 和 PostgreSQL 类型处理的资源和参考
- 有关在 Spring Boot 中处理枚举和类型不匹配的深入信息,以及 CriteriaBuilder 使用的实际示例: Baeldung - JPA 标准查询
- 有关常见 PostgreSQL 错误的指南以及在 Java 应用程序中使用枚举进行类型转换的最佳实践: PostgreSQL 文档 - 类型转换
- 详细的 Spring Boot 文档涵盖了用于字段类型处理的本机查询和注释: Spring Data JPA 参考