Spring CRUDRepository在Postgres中保存嵌套JSON列报错求助
嘿,我之前也踩过Hibernate+Postgres JSON存储的坑,给你梳理下可能的问题和解决办法,应该能帮到你~
问题分析与解决步骤
1. 先确认数据库基础配置
首先得确保Postgres里对应inputs的字段类型是**jsonb**(优先推荐,支持索引和高效查询)或者json。如果字段类型是普通的varchar或者其他类型,肯定会触发序列化/存储错误,这是最容易忽略的基础项。
2. 检查自定义Hibernate UserType的实现(如果用了自定义类型)
自定义UserType是核心,几个关键方法必须写对:
nullSafeGet:从数据库读取JSON字符串,反序列化为你的自定义实体列表nullSafeSet:把实体列表序列化为JSON字符串,写入数据库sqlTypes()要返回适配Postgres JSON的类型码
给你一个靠谱的示例实现:
import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.SerializationException; import org.hibernate.usertype.UserType; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List; public class JsonListUserType implements UserType { // 最好用Spring容器注入的ObjectMapper,避免配置不一致 private final ObjectMapper objectMapper = new ObjectMapper(); @Override public int[] sqlTypes() { return new int[]{Types.OTHER}; // 适配Postgres的json/jsonb类型 } @Override public Class<?> returnedClass() { return List.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == y) return true; if (x == null || y == null) return false; return x.equals(y); } @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { String json = rs.getString(names[0]); if (json == null) return null; try { // 替换成你的自定义输入实体类 return objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, YourCustomInputEntity.class)); } catch (Exception e) { throw new SerializationException("反序列化JSON失败: " + json, e); } } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.OTHER); return; } try { String json = objectMapper.writeValueAsString(value); st.setObject(index, json, Types.OTHER); } catch (Exception e) { throw new SerializationException("序列化对象为JSON失败", e); } } @Override public Object deepCopy(Object value) throws HibernateException { if (value == null) return null; try { String json = objectMapper.writeValueAsString(value); return objectMapper.readValue(json, returnedClass()); } catch (Exception e) { throw new SerializationException("深拷贝对象失败", e); } } @Override public boolean isMutable() { return true; } @Override public Serializable disassemble(Object value) throws HibernateException { return (Serializable) deepCopy(value); } @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return deepCopy(cached); } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return deepCopy(original); } }
3. 实体类的字段映射配置
在Transaction实体里,给inputs字段加上正确的注解:
import javax.persistence.*; import org.hibernate.annotations.Type; import java.util.List; @Entity @Table(name = "transactions") public class Transaction { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String hash; // 指定自定义UserType,同时声明数据库字段类型为jsonb @Type(type = "com.yourpackage.JsonListUserType") @Column(columnDefinition = "jsonb") private List<YourCustomInputEntity> inputs; // 构造器、getter、setter省略 }
更简单的替代方案(推荐)
如果你的Spring Boot版本比较新(2.4+),完全可以不用自定义UserType,直接用Hibernate自带的@JdbcTypeCode注解,自动用Jackson处理序列化:
import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; import javax.persistence.*; import java.util.List; @Entity @Table(name = "transactions") public class Transaction { // ...其他字段 @Column(columnDefinition = "jsonb") @JdbcTypeCode(SqlTypes.JSON) private List<YourCustomInputEntity> inputs; // ... }
这种方式减少自定义代码,避免很多潜在的坑。
4. 检查Jackson配置(关键)
确保Jackson能正确序列化/反序列化你的自定义实体:
- 实体类要有无参构造器(Jackson默认需要)
- 如果有特殊类型(比如Java 8日期、枚举),要给Jackson配置对应的模块:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); // 处理Java 8日期类型 mapper.registerModule(new JavaTimeModule()); mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); return mapper; } }
5. Spring Data Repository的使用
确保你的Repository定义正确,调用save()方法时没有额外的错误:
import org.springframework.data.repository.CrudRepository; public interface TransactionRepository extends CrudRepository<Transaction, Long> { }
保存示例:
@Service public class TransactionService { private final TransactionRepository transactionRepository; // 构造器注入 public TransactionService(TransactionRepository transactionRepository) { this.transactionRepository = transactionRepository; } public Transaction saveTransaction(Transaction transaction) { return transactionRepository.save(transaction); } }
常见报错排查
- SQLGrammarException:检查数据库字段类型是否为
jsonb/json,实体类的@Column定义是否匹配 - SerializationException:检查Jackson能否序列化你的自定义实体,比如有没有无参构造器、字段是否可访问
- ClassCastException:检查
nullSafeGet里的反序列化类型是否和实体字段类型完全匹配
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

