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

JPA实体中JSON类型Map字段持久化成功但查询失败的问题排查

问题:Oracle JSON列与EclipseLink转换器单向生效问题

需要将标识名称+布尔状态的键值对配置信息以JSON格式持久化到Oracle数据库,写入操作正常,但查询时抛出SQL异常,且转换器的convertToEntityAttribute方法从未被调用。


数据库表结构

CREATE TABLE "SCHEMA"."CONFIG" (    
    "ID" NUMBER(38,0) NOT NULL ENABLE, 
    "ITEMS" JSON DEFAULT '' NOT NULL ENABLE, 
     CONSTRAINT "PK_CONFIG" PRIMARY KEY ("ID")
   )

实体类定义

@Entity
@Table(name = "CONFIG")
@SequenceGenerator(name = "ConfigIdGenerator", sequenceName = "CONFIG_ID_SEQ", allocationSize = 1)
public class ConfigEntity {
    
    private long id;
    private Map<String, Boolean> items;

    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ConfigIdGenerator")
    @Column(name = "ID", nullable = false)
    @Id
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Convert(converter = ConfigItemsConverter.class)
    @Column(name = "ITEMS", nullable = false, columnDefinition = "JSON")
    public Map<String, Boolean> getItems() {
        return items;
    }

    public void setItems(Map<String, Boolean> items) {
        this.items = items;
    }   
}

自定义属性转换器

@Converter
public class ConfigItemsConverter implements AttributeConverter<Map<String, Boolean>, String> {
    
    private static final ObjectMapper MAPPER = new ObjectMapper();
    
    @Override
    public String convertToDatabaseColumn(Map<String, Boolean> value) {
        try {
            return MAPPER.writeValueAsString(value);
        } catch (JsonProcessingException ex) {
            // Should not happen. Map values should be in correct format due to generics.
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    public Map<String, Boolean> convertToEntityAttribute(String dbValue) {
        try {
            return MAPPER.readValue(dbValue, new TypeReference<Map<String, Boolean>>() {});
        } catch (JsonProcessingException ex) {
            // Should not happen. DB data are in JSON format due to JSON datatype
            ex.printStackTrace();
        }
        return null;
    }

}

查询时抛出的异常

jakarta.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 4.0.2.v202306161219): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Invalid column type: getOracleObject not implemented for class oracle.jdbc.driver.T4CJsonAccessor
Error Code: 17004
Call: SELECT * FROM (SELECT /*+ FIRST_ROWS */ a.*, ROWNUM rnum  FROM (SELECT t1.ID AS a1, t1.ITEMS AS a2 FROM CONFIG t1 ) a WHERE ROWNUM <= ?) WHERE rnum > ?
    bind => [1, 0]
Query: ReadAllQuery(name="ConfigEntity.FIND_ALL" referenceClass=ConfigEntity sql="SELECT * FROM (SELECT /*+ FIRST_ROWS */ a.*, ROWNUM rnum  FROM (SELECT t1.ID AS a1, t1.ITEMS AS a2 FROM CONFIG t1 ) a WHERE ROWNUM <= ?) WHERE rnum > ?")
    at org.eclipse.persistence.internal.jpa.QueryImpl.getDetailedException(QueryImpl.java:392) ~[org.eclipse.persistence.jpa-4.0.2.jar:?]
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:265) ~[org.eclipse.persistence.jpa-4.0.2.jar:?]
    at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:483) ~[org.eclipse.persistence.jpa-4.0.2.jar:?]
    at jakarta.persistence.TypedQuery.getResultStream(TypedQuery.java:93) ~[jakarta.persistence-api-3.2.0.jar:3.2.0]
    ...
Caused by: java.sql.SQLException: Invalid column type: getOracleObject not implemented for class oracle.jdbc.driver.T4CJsonAccessor
    at oracle.jdbc.driver.GeneratedAccessor.getOracleObject(GeneratedAccessor.java:1221) ~[ojdbc11-21.10.0.0.jar:21.10.0.0.0]
    at oracle.jdbc.driver.JsonAccessor.getObject(JsonAccessor.java:263) ~[ojdbc11-21.10.0.0.jar:21.10.0.0.0]
    at oracle.jdbc.driver.GeneratedStatement.getObject(GeneratedStatement.java:196) ~[ojdbc11-21.10.0.0.jar:21.10.0.0.0]
    at oracle.jdbc.driver.GeneratedScrollableResultSet.getObject(GeneratedScrollableResultSet.java:334) ~[ojdbc11-21.10.0.0.jar:21.10.0.0.0]
    at oracle.ucp.jdbc.proxy.oracle.ResultSetProxy.getObject(ResultSetProxy.java:152) ~[ucp11-21.10.0.0.jar:21.10.0.0.0]
    at oracle.ucp.jdbc.proxy.oracle$1ucp$1jdbc$1proxy$1oracle$1ResultSetProxy$2oracle$1jdbc$1internal$1OracleResultSet$$$Proxy.getObject(Unknown Source) ~[ucp11-21.10.0.0.jar:21.10.0.0.0]
    at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.getObjectFromResultSet(DatabasePlatform.java:1230) ~[org.eclipse.persistence.core-4.0.2.jar:?]
    at org.eclipse.persistence.platform.database.OraclePlatform.getObjectFromResultSet(OraclePlatform.java:575) ~[org.eclipse.persistence.core-4.0.2.jar:?]
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.getObject(DatabaseAccessor.java:1375) ~[org.eclipse.persistence.core-4.0.2.jar:?]
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.fetchRow(DatabaseAccessor.java:1094) ~[org.eclipse.persistence.core-4.0.2.jar:?]

核心疑问

  • 为何转换器仅写入方向生效,查询时不触发convertToEntityAttribute?
  • 缺少哪些配置导致该问题?
  • 如何修复?

问题原因与修复方案

原因分析

EclipseLink对Oracle的原生JSON列有内置处理逻辑,它会将JSON类型列默认映射为Oracle的OracleJsonValue类型,而非字符串。此时@Convert注解会被忽略——JPA提供者优先处理原生数据库类型映射,不会调用自定义转换器,导致查询时尝试直接将OracleJsonValue转换为Map<String, Boolean>,引发JDBC类型转换异常。

修复方案

方案1:修改列映射,强制按字符串处理

在@Column注解中指定jdbcType为VARCHAR,告诉EclipseLink将JSON列当作字符串处理,从而触发自定义转换器:

import jakarta.persistence.JDBCType;

// ...

@Convert(converter = ConfigItemsConverter.class)
@Column(name = "ITEMS", nullable = false, columnDefinition = "JSON", jdbcType = JDBCType.VARCHAR)
public Map<String, Boolean> getItems() {
    return items;
}

方案2:使用EclipseLink的JSON原生映射支持

如果不想依赖自定义转换器,可以直接利用EclipseLink的原生JSON处理能力:

  1. 移除@Convert注解
  2. 修改字段映射,指定jdbcType为OTHER并添加@Type注解:
import jakarta.persistence.JDBCType;
import org.eclipse.persistence.annotations.Type;

// ...

@Column(name = "ITEMS", nullable = false, columnDefinition = "JSON", jdbcType = JDBCType.OTHER)
@Type(type = "json")
public Map<String, Boolean> getItems() {
    return items;
}

注:此方案要求EclipseLink版本≥2.7,确保原生JSON映射功能可用。

方案3:调整版本兼容性

异常中的getOracleObject not implemented可能是JDBC驱动与EclipseLink版本不匹配导致的,尝试升级EclipseLink到4.0.3+或ojdbc11到21.11.0.0+,确保两者版本兼容。

验证

修复后执行查询,确认convertToEntityAttribute方法被调用,且能正常将JSON字符串转换为Map<String, Boolean>,无类型转换异常。


内容的提问来源于stack exchange,提问作者Jan Stanicek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:34:50