HsqlException:不兼容的数据类型组合

Posted

技术标签:

【中文标题】HsqlException:不兼容的数据类型组合【英文标题】:HsqlException: incompatible data types in combination 【发布时间】:2020-12-13 08:59:27 【问题描述】:

由于我将集成测试移至内存中的 HSQLDB(希望避免每次都必须提供正确的 mysql 并在每次测试后清除数据库),因此每次使用 CrudRepository#findById() 方法时都会收到此错误消息叫

Caused by: org.hsqldb.HsqlException: incompatible data types in combination
    at org.hsqldb.error.Error.error(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
    at org.hsqldb.error.Error.error(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
    at org.hsqldb.types.CharacterType.getAggregateType(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
    at org.hsqldb.types.Type.getAggregateType(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]

GET /IDPATCH /IDDELETE /ID 调用 REST API(弹簧数据休息)就是这种情况。外部例外是

Caused by: java.sql.SQLSyntaxErrorException: incompatible data types in combination in statement [select trainingre0_.id as id1_15_0_, trainingre0_.matching_rule_id as matching3_15_0_, trainingre0_.tenant as tenant2_15_0_, matchingru1_.id as id1_2_1_, matchingru1_.mode as mode1_0_1_, matchingru1_.mode as mode1_3_1_, matchingru1_.property as property2_3_1_, matchingru1_.value as value3_3_1_, matchingru1_.ignore_case as ignore_c1_6_1_, matchingru1_.mode as mode2_6_1_, matchingru1_.property as property3_6_1_, matchingru1_.value as value4_6_1_, matchingru1_.clazz_ as clazz_1_, matchingru2_.composite_matching_rule_id as composit1_1_2_, matchingru3_.id as matching2_1_2_, matchingru3_.id as id1_2_3_, matchingru3_.mode as mode1_0_3_, matchingru3_.mode as mode1_3_3_, matchingru3_.property as property2_3_3_, matchingru3_.value as value3_3_3_, matchingru3_.ignore_case as ignore_c1_6_3_, matchingru3_.mode as mode2_6_3_, matchingru3_.property as property3_6_3_, matchingru3_.value as value4_6_3_, matchingru3_.clazz_ as clazz_3_ from training_request_subscription trainingre0_ left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru1_ on trainingre0_.matching_rule_id=matchingru1_.id left outer join composite_matching_rule_matching_rules matchingru2_ on matchingru1_.id=matchingru2_.composite_matching_rule_id left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru3_ on matchingru2_.matching_rules_id=matchingru3_.id where trainingre0_.id=?]

但让我向您展示希望使其更清晰的域模型:

TrainingRequestSubscription(读取时产生错误的实体 I):

@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@RequiredArgsConstructor
@ValidTrainingRequestSubscription
public class TrainingRequestSubscription extends AbstractBaseEntity implements TenantScoped 

    @NotNull
    @JsonProperty(access = Access.WRITE_ONLY)
    private String tenant;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @NotNull
    private @NonNull MatchingRule matchingRule;


匹配规则

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Getter
@Setter
@NoArgsConstructor
// @formatter:off
@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes( 
  @Type(value = NumberMatchingRule.class, name = "number"), 
  @Type(value = StringMatchingRule.class, name = "string"),
  @Type(value = CompositeMatchingRule.class, name = "composite")
)
//@formatter:on
public abstract class MatchingRule extends AbstractBaseEntity 

    @Transient
    @JsonIgnore
    public abstract Set<String> getProperties();

    @Transient
    @JsonProperty(access = Access.READ_ONLY)
    public abstract String getType();


NumberMatchingRule(这是我正在测试的匹配规则的实现)

@Entity
@Data
@Builder
@NoArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class NumberMatchingRule extends MatchingRule 

    public static enum Mode 
        LESS, LESS_EQUAL, EQUAL, GREATER_EQUAL, GREATER
    

    @Enumerated(EnumType.STRING)
    @Builder.Default
    private @NonNull Mode mode = Mode.EQUAL;

    @NotNull
    private @NonNull Double value;

    @NotNull
    @Size(min = 1)
    private @NonNull String property;

    @Override
    public Set<String> getProperties() 
        return Set.of(property);
    

    @Transient
    @JsonProperty(access = Access.READ_ONLY)
    public String getType() 
        return "number";
    


这一切都在 MySQL 上运行良好,堆栈跟踪没有给我任何可能导致问题的方向。所以我在这里依靠你的经验。

也许发布 repo 本身也很有用,因为它可以包含影响行为的 @Query 注释,但我只有在 @Query 上不应该涉及的 findAll() 方法上。事实上,使用 findAll() 的测试会成功。

@CrossOrigin
public interface TrainingRequestSubscriptionRepo extends CrudRepository<TrainingRequestSubscription, UUID> 

    @PreAuthorize("isFullyAuthenticated() and hasAnyScopeFor('trainingrequestsubscription', 'read')")
    @Query("SELECT e FROM ##entityName e WHERE CONCAT(e.tenant.id, '') IN ?#security.getTenants('trainingrequestsubscription', 'r') OR '*' IN ?#security.getTenants('trainingrequestsubscription', 'r')")
    @Override
    Set<TrainingRequestSubscription> findAll();

    @PreAuthorize("isFullyAuthenticated() and hasAnyScopeFor('trainingrequestsubscription', 'read')")
    @PostAuthorize("hasPermission(returnObject, 'read')")
    @Override
    Optional<TrainingRequestSubscription> findById(UUID id);

    // @formatter:off
    @PreAuthorize(
    "isFullyAuthenticated() and " +
    "(" +
        "(#entity.id == null and hasPermission(#entity, 'create'))" + " or " +
        "(#entity.id != null and hasPermission(#entity, 'update'))" +
    ")")
    // @formatter:on
    @Override
    <S extends TrainingRequestSubscription> S save(@Param("entity") S entity);

    @PreAuthorize("isFullyAuthenticated() and hasPermission(#entity, 'delete')")
    @Override
    void delete(@Param("entity") TrainingRequestSubscription entity);


编辑: 正如 cmets 中所建议的,我在服务器模式下使用 --silent false 和 `--trace true`` 运行 HSQLDB 以查看传入的请求是什么:

[Server@2e0fa5d3]: 0:SQLCLI:SQLPREPARE insert into number_matching_rule (mode, property, value, id) values (?, ?, ?, ?)
[Server@2e0fa5d3]: 0:SQLCLI:SQLEXECUTE:1
[Server@2e0fa5d3]: 0:SQLCLI:SQLFREESTMT:1
[Server@2e0fa5d3]: 0:SQLCLI:SQLPREPARE insert into training_request_subscription (matching_rule_id, tenant, id) values (?, ?, ?)
[Server@2e0fa5d3]: 0:SQLCLI:SQLEXECUTE:2
[Server@2e0fa5d3]: 0:SQLCLI:SQLFREESTMT:2
[Server@2e0fa5d3]: 0:SQLCLI:SQLENDTRAN:COMMIT
[Server@2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server@2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server@2e0fa5d3]: 0:HSQLCLI:GETSESSIONATTR
[Server@2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server@2e0fa5d3]: 0:SQLCLI:SQLPREPARE select trainingre0_.id as id1_15_0_, trainingre0_.matching_rule_id as matching3_15_0_, trainingre0_.tenant as tenant2_15_0_, matchingru1_.id as id1_2_1_, matchingru1_.mode as mode1_0_1_, matchingru1_.mode as mode1_3_1_, matchingru1_.property as property2_3_1_, matchingru1_.value as value3_3_1_, matchingru1_.ignore_case as ignore_c1_6_1_, matchingru1_.mode as mode2_6_1_, matchingru1_.property as property3_6_1_, matchingru1_.value as value4_6_1_, matchingru1_.clazz_ as clazz_1_, matchingru2_.composite_matching_rule_id as composit1_1_2_, matchingru3_.id as matching2_1_2_, matchingru3_.id as id1_2_3_, matchingru3_.mode as mode1_0_3_, matchingru3_.mode as mode1_3_3_, matchingru3_.property as property2_3_3_, matchingru3_.value as value3_3_3_, matchingru3_.ignore_case as ignore_c1_6_3_, matchingru3_.mode as mode2_6_3_, matchingru3_.property as property3_6_3_, matchingru3_.value as value4_6_3_, matchingru3_.clazz_ as clazz_3_ from training_request_subscription trainingre0_ left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru1_ on trainingre0_.matching_rule_id=matchingru1_.id left outer join composite_matching_rule_matching_rules matchingru2_ on matchingru1_.id=matchingru2_.composite_matching_rule_id left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru3_ on matchingru2_.matching_rules_id=matchingru3_.id where trainingre0_.id=?

对我来说,它看起来很像 Hibernate 记录的内容(见上文)。老实说,我不知道它应该是什么样子,所以我很难说是哪里出了问题。

编辑: @fredt 的回答建议number_matching_rule 表的property 列不是varchar 类型。以下 HSQL 数据库管理器显示的屏幕截图实际上是varchar

【问题讨论】:

你需要找出并报告Spring生成的SQL查询编译失败。一种方法是使用quiet=false 在服务器模式下运行 HSQLDB,并检查提交到服务器的查询。 谢谢,@fredt。请检查上面的编辑。 【参考方案1】:

错误消息在包含 UNION ALL 的子查询之一中引发,并且与表中名为 property 的列的类型有关,例如 number_matching_rule。第一个 SELECT 将类型定义为 varchar(100) 并且名为 property 的表列的类型不是字符串(VARCHAR 等)。

例如:

select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule 
union all 
select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule 
union all 
select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule 

检查生成的表并查看property 列使用的SQL 类型。还要检查其他列的类型兼容性。

如果查询是手写的,则 CAST(property AS VARCHAR(100)) 之类的转换会将列转换为所需的类型。

编辑:

您可以在 DatabaseManager 中检查查询:

EXPLAIN PLAN FOR select trainingre0_.id ...

如果抛出异常,分别尝试包含联合的两个子查询并简化,直到找到导致异常的列。

【讨论】:

感谢您的跟进。我在原始帖子中附加了一个屏幕截图,证明property 列的类型为varchar。如果我理解正确,您怀疑情况并非如此。 number_matching_rule 表中的其他列是:ID (varchar)、MODE (varchar)、VALUE (DOUBLE)。 string_matching_rule 具有 ID (varchar)、IGNORE_CASE (boolean)、MODE (varchar)、PROPERTY (varchar)、VALUE (varchar)。两个表中的value 列不同。会是这个原因吗?

以上是关于HsqlException:不兼容的数据类型组合的主要内容,如果未能解决你的问题,请参考以下文章

HSQLDB客户端版本不兼容

Java HQL org.hsqldb.HsqlException:用户缺少权限或找不到对象

在 prepareStatement 中使用 COALESCE 来“插入”cmd

Java HashSet和数据类型Short,不兼容?

组合数据类型练习,英文词频统计实例上

原因:org.hsqldb.HsqlException: invalid statemnet - 导入 CSV 数据时需要文本表