使用Hibernate将JSONB字段持久化到H2数据库时遇转换错误
解决H2数据库下Hibernate持久化JSONB类型的转换错误问题
我之前也踩过跨数据库适配JSON类型的坑,结合你提到的H2源码里ValueJson.convertToJson不处理JAVA_OBJECT类型的问题,给你几个可行的解决方向:
1. 为H2单独实现适配的自定义JSON类型
你的PGJsonType是针对PostgreSQL的PGObject设计的,但H2对JSON的处理逻辑完全不同,得给H2单独写一个类型处理器绕开这个转换问题。
创建一个H2JsonType类,继承Hibernate的AbstractSingleColumnStandardBasicType,核心是把Java对象序列化为JSON字符串传给H2(H2的JSON类型可以直接接收字符串输入):
import com.fasterxml.jackson.databind.ObjectMapper; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.descriptor.java.StringTypeDescriptor; import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; public class H2JsonType extends AbstractSingleColumnStandardBasicType<Object> { private static final ObjectMapper objectMapper = new ObjectMapper(); public H2JsonType() { super(VarcharTypeDescriptor.INSTANCE, new JsonJavaTypeDescriptor()); } @Override public String getName() { return "json"; } private static class JsonJavaTypeDescriptor extends StringTypeDescriptor { @Override public String toString(Object value) { try { return objectMapper.writeValueAsString(value); } catch (Exception e) { throw new IllegalArgumentException("Could not convert object to JSON string", e); } } @Override public Object fromString(String string) { try { return objectMapper.readValue(string, Object.class); } catch (Exception e) { throw new IllegalArgumentException("Could not convert JSON string to object", e); } } } }
2. 让Hibernate根据数据库方言自动切换类型
接下来要配置Hibernate在不同数据库环境下使用对应的类型:
- 如果是Spring Boot,可以通过配置类动态注册类型:
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributor; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.service.ServiceRegistry; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class JsonTypeConfiguration { @Conditional(H2DatabaseCondition.class) public static class H2JsonTypeContributor implements TypeContributor { @Override public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { typeContributions.contributeType(new H2JsonType()); } } @Conditional(PostgresDatabaseCondition.class) public static class PostgresJsonTypeContributor implements TypeContributor { @Override public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { typeContributions.contributeType(new PGJsonType()); } } // 实现H2DatabaseCondition和PostgresDatabaseCondition,通过环境变量或数据源URL判断当前数据库类型 }
- 如果是传统JPA的
persistence.xml,可以用hibernate.types.per-platform属性指定不同数据库的类型:
<property name="hibernate.types.per-platform" value=" PostgreSQL:com.yourpackage.PGJsonType, H2:com.yourpackage.H2JsonType "/>
3. 开启H2的PostgreSQL兼容模式
在H2的连接URL里添加MODE=PostgreSQL参数,让H2尽量模拟PostgreSQL的JSON行为,这能减少不少兼容性问题:
spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_ON_EXIT=FALSE
另外,确保你用的是最新版的H2数据库,旧版本的JSON处理确实存在不少bug,升级到最新稳定版可能直接解决ValueJson.convertToJson的类型处理问题。
4. 改造现有PGJsonType兼容H2
如果不想单独写一个类型,也可以修改你的PGJsonType,在nullSafeSet方法里判断当前数据库方言做分支处理:
@Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.OTHER); return; } Dialect dialect = session.getJdbcServices().getDialect(); ObjectMapper objectMapper = new ObjectMapper(); if (dialect instanceof H2Dialect) { // H2环境下转成JSON字符串传入 String jsonStr = objectMapper.writeValueAsString(value); st.setString(index, jsonStr); } else { // PostgreSQL环境下保留原PGObject逻辑 PGObject jsonObject = new PGObject(); jsonObject.setType("jsonb"); jsonObject.setValue(objectMapper.writeValueAsString(value)); st.setObject(index, jsonObject); } }
最后记得测试验证:调用EntityManager.persist后,检查H2数据库的JSON列是否正确存储数据,同时确保PostgreSQL环境下的功能不受影响。
内容的提问来源于stack exchange,提问作者sancho
相关产品推荐
相关产品推荐

