You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Quarkus PanacheEntity实现动态列实体遇JdbcType异常求方案

解决Hibernate ORM with Panache中Map<String, String>的JdbcType映射异常

你遇到的JdbcTypeRecommendationException是因为Hibernate默认无法自动推断Map<String, String>类型对应的数据库列类型,需要明确指定映射策略。以下是几种可行的解决办法:

方案一:将Map映射为关联表的键值对

使用JPA的@ElementCollection注解,把Map中的每个键值对存储到单独的关联表中,适合需要单独查询键或值的场景:

修改实体类的fields字段,添加相关注解:

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.Column;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Entity
@Table(name = "table")
public class MyEntity extends PanacheEntity {

  @ElementCollection
  @MapKeyColumn(name = "field_key") // 指定存储键的列名
  @Column(name = "field_value")     // 指定存储值的列名
  private Map<String, String> fields = new HashMap<>();

  // 以下getter、setter和序列化方法保持不变
  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  public <T> T getField(String key, Class<T> valueType) {
    String value = fields.get(key);
    try {
      return OBJECT_MAPPER.readValue(value, valueType);
    } catch (Exception e) {
      throw new RuntimeException("Failed to deserialize value for key: " + key, e);
    }
  }

  public void setField(String key, Object value) {
    try {
      String stringValue = OBJECT_MAPPER.writeValueAsString(value);
      fields.put(key, stringValue);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Failed to serialize value for key: " + key, e);
    }
  }

  public Map<String, String> getFields() {
    return fields;
  }

  public void setFields(Map<String, String> fields) {
    this.fields = fields;
  }
}

这种方式会自动生成一个名为table_fields的关联表(命名规则:主表名_字段名),每行存储一个键值对,主表通过外键关联。

方案二:将Map序列化为JSON字符串存储到单个列

如果不需要单独查询键值对,只是想存储动态字段,推荐把整个Map序列化为JSON字符串,存到数据库的TEXT或JSON类型列中:

步骤1:自定义AttributeConverter

创建一个转换器,负责Map和JSON字符串之间的序列化/反序列化:

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;

@Converter(autoApply = false)
public class StringMapJsonConverter implements AttributeConverter<Map<String, String>, String> {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Map<String, String> attribute) {
        if (attribute == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(attribute);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize map to JSON", e);
        }
    }

    @Override
    public Map<String, String> convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isEmpty()) {
            return new HashMap<>();
        }
        try {
            return OBJECT_MAPPER.readValue(dbData, Map.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to deserialize JSON to map", e);
        }
    }
}

步骤2:在实体类中使用转换器

修改fields字段的注解,指定转换器和列类型:

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Entity
@Table(name = "table")
public class MyEntity extends PanacheEntity {

  @Column(columnDefinition = "TEXT") // 用TEXT存储JSON,也可以用数据库原生JSON类型(如POSTGRESQL的JSONB)
  @Convert(converter = StringMapJsonConverter.class)
  private Map<String, String> fields = new HashMap<>();

  // 以下方法保持不变
  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  public <T> T getField(String key, Class<T> valueType) {
    String value = fields.get(key);
    try {
      return OBJECT_MAPPER.readValue(value, valueType);
    } catch (Exception e) {
      throw new RuntimeException("Failed to deserialize value for key: " + key, e);
    }
  }

  public void setField(String key, Object value) {
    try {
      String stringValue = OBJECT_MAPPER.writeValueAsString(value);
      fields.put(key, stringValue);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Failed to serialize value for key: " + key, e);
    }
  }

  public Map<String, String> getFields() {
    return fields;
  }

  public void setFields(Map<String, String> fields) {
    this.fields = fields;
  }
}

如果你的数据库支持原生JSON类型(比如PostgreSQL的JSONB),可以把columnDefinition改成JSONB,这样还能利用数据库的JSON查询能力。

方案三:使用Hibernate 6+的原生JSON类型支持

如果使用Hibernate 6及以上版本,可以直接通过@Type注解指定JSON类型,无需自定义转换器:

首先确保引入了jackson-datatype-hibernate6依赖,然后修改实体类:

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.Column;
import org.hibernate.annotations.Type;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Entity
@Table(name = "table")
public class MyEntity extends PanacheEntity {

  @Column(columnDefinition = "JSONB") // 根据数据库选择合适的JSON类型
  @Type(com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module.class)
  private Map<String, String> fields = new HashMap<>();

  // 以下方法保持不变
  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  public <T> T getField(String key, Class<T> valueType) {
    String value = fields.get(key);
    try {
      return OBJECT_MAPPER.readValue(value, valueType);
    } catch (Exception e) {
      throw new RuntimeException("Failed to deserialize value for key: " + key, e);
    }
  }

  public void setField(String key, Object value) {
    try {
      String stringValue = OBJECT_MAPPER.writeValueAsString(value);
      fields.put(key, stringValue);
    } catch (JsonProcessingException e) {
      throw new RuntimeException("Failed to serialize value for key: " + key, e);
    }
  }

  public Map<String, String> getFields() {
    return fields;
  }

  public void setFields(Map<String, String> fields) {
    this.fields = fields;
  }
}

选择建议

  • 如果需要对动态字段做单独的查询、过滤操作,优先选方案一;
  • 如果只是存储动态配置、元数据等不需要单独查询的内容,方案二或方案三更简洁高效。

内容的提问来源于stack exchange,提问作者Biggy Poopa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:22:04