MyBatis无法映射PostgreSQL JSONB至Map/JsonNode的问题解决
解决PostgreSQL JSONB字段映射为Map<String, Object>的问题
方法1:自定义MyBatis TypeHandler(推荐方案)
MyBatis默认无法直接识别PostgreSQL的JSONB类型,需要自定义类型处理器完成JSONB与Map的双向转换:
1.1 编写TypeHandler类
import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.postgresql.util.PGobject; import com.fasterxml.jackson.databind.ObjectMapper; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; public class JsonbTypeHandler extends BaseTypeHandler<Map<String, Object>> { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final PGobject JSONB_OBJECT = new PGobject(); @Override public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException { JSONB_OBJECT.setType("jsonb"); JSONB_OBJECT.setValue(OBJECT_MAPPER.writeValueAsString(parameter)); ps.setObject(i, JSONB_OBJECT); } @Override public Map<String, Object> getNullableResult(ResultSet rs, String columnName) throws SQLException { return parseJson(rs.getString(columnName)); } @Override public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parseJson(rs.getString(columnIndex)); } @Override public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parseJson(cs.getString(columnIndex)); } private Map<String, Object> parseJson(String json) throws SQLException { if (json == null || json.isBlank()) { return null; } try { return OBJECT_MAPPER.readValue(json, Map.class); } catch (Exception e) { throw new SQLException("JSONB转Map失败", e); } } }
1.2 关联TypeHandler到字段
有两种方式绑定:
- 方式A:在DTO字段上标注
@Data @Accessors(chain = true) public class UserHasAssetDTO { private Integer id; ... @ResultType(JsonbTypeHandler.class) private Map<String, Object> props; }
- 方式B:在Repository查询注解中指定
@Select("select id, ..., props from bla bla bla") @Results({ @Result(column = "props", property = "props", typeHandler = JsonbTypeHandler.class) }) List<UserHasAssetDTO> getALLAssetsFilteredByDate(@Param("username") String username, @Param("startDate") String startDate, @Param("endDate") String endDate);
1.3 依赖检查
确保项目引入必要依赖:
<!-- PostgreSQL驱动 --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.6.0</version> </dependency> <!-- Jackson JSON处理包 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency>
方法2:使用MyBatis-Plus内置TypeHandler(若用MyBatis-Plus)
如果项目基于MyBatis-Plus开发,可以直接用框架内置的类型处理器:
@Data @Accessors(chain = true) public class UserHasAssetDTO { private Integer id; ... @TableField(typeHandler = JacksonTypeHandler.class) private Map<String, Object> props; }
方法3:手动转换(临时应急方案)
若不想编写TypeHandler,可临时通过SQL转换+Service层手动解析:
- 修改Repository查询SQL,将JSONB转为字符串:
@Select("select id, ..., props::text as props from bla bla bla") List<UserHasAssetDTO> getALLAssetsFilteredByDate(@Param("username") String username, @Param("startDate") String startDate, @Param("endDate") String endDate);
- 临时将DTO的
props字段改为String类型,在Service层转为Map:
ObjectMapper objectMapper = new ObjectMapper(); List<UserHasAssetDTO> dtoList = userHasAssetRepository.getALLAssetsFilteredByDate(username, startDate, endDate); for (UserHasAssetDTO dto : dtoList) { String propsStr = dto.getProps(); if (propsStr != null) { Map<String, Object> propsMap = objectMapper.readValue(propsStr, Map.class); // 可将DTO改为包含String和Map两种props字段,或新建VO返回 } }
注意事项
- 确保PostgreSQL驱动版本不低于42.x,旧版本对JSONB的支持不完善
- 自定义TypeHandler需被MyBatis扫描到,可通过配置
typeHandlersPackage或添加@Component注解(Spring环境)
内容的提问来源于stack exchange,提问作者Kambei
相关产品推荐
相关产品推荐

