Java Spring Boot查询PostgreSQL时如何保持Map顺序并输出JSON
问题
使用Java(Spring Boot)查询PostgreSQL数据库时,将查询结果存入Map<String, Object>无法保留字段顺序,但生成报表需要严格保持SQL查询中定义的字段顺序。当前查询代码如下:
@Query(value = "select f.entity_id as \"NIT\", f.subpoena_number as \"Número Comparendo\", f.plate as \"Placa\", f.code as \"Código Infracción\", f.infringement_date \"Fecha Infracción\", f.amount as \"Valor\", f.city as \"Ciudad\", f.address as \"Dirección\", f.upload_date as \"Fecha del cargue\", f.notification_status as \"Estado Notificación\", f.process_status as \"Estado Proceso\", f.source_data as \"Origen\", case when f.paying = true then 'PAGADO' else 'SIN PAGAR' end as \"Estado Pago\", f.payment_date as \"Fecha Pago\" from renting.fine f inner join renting.company c on c.nit = f.entity_id and c.companyparent_id = f.companyparent inner join renting.notification n on f.id = n.fine_id where f.companyparent = :companyParent and f.upload_date > :lastUpdate and f.upload_date < :endUpdate ", nativeQuery = true) List<Map<String , Object>> generateComparingReport(@Param("lastUpdate") LocalDateTime lastUpdate, @Param("endUpdate") LocalDateTime endUpdate, @Param("companyParent") String companyParent);
使用List<String[]>可以保留顺序,但需要输出JSON格式,如何按需排序元素并保留JSON数据类型?
解决方案
方法1:用LinkedHashMap替代无序HashMap
Spring Data JPA默认用HashMap存储查询结果,它不保证顺序。可以通过自定义结果转换器,让结果存入LinkedHashMap(按插入顺序保存)。
- 自定义Hibernate结果转换器:
public class OrderedResultTransformer extends BasicTransformerAdapter { @Override public Object transformTuple(Object[] tuple, String[] aliases) { Map<String, Object> orderedMap = new LinkedHashMap<>(); for (int i = 0; i < aliases.length; i++) { orderedMap.put(aliases[i], tuple[i]); } return orderedMap; } }
- 手动执行查询并应用转换器:
@Autowired private EntityManager entityManager; public List<Map<String, Object>> getOrderedReport(LocalDateTime lastUpdate, LocalDateTime endUpdate, String companyParent) { String sql = "select f.entity_id as \"NIT\", f.subpoena_number as \"Número Comparendo\", f.plate as \"Placa\", f.code as \"Código Infracción\", f.infringement_date \"Fecha Infracción\", f.amount as \"Valor\", f.city as \"Ciudad\", f.address as \"Dirección\", f.upload_date as \"Fecha del cargue\", f.notification_status as \"Estado Notificación\", f.process_status as \"Estado Proceso\", f.source_data as \"Origen\", case when f.paying = true then 'PAGADO' else 'SIN PAGAR' end as \"Estado Pago\", f.payment_date as \"Fecha Pago\" from renting.fine f inner join renting.company c on c.nit = f.entity_id and c.companyparent_id = f.companyparent inner join renting.notification n on f.id = n.fine_id where f.companyparent = :companyParent and f.upload_date > :lastUpdate and f.upload_date < :endUpdate "; return entityManager.createNativeQuery(sql) .setParameter("lastUpdate", lastUpdate) .setParameter("endUpdate", endUpdate) .setParameter("companyParent", companyParent) .unwrap(org.hibernate.query.Query.class) .setResultTransformer(new OrderedResultTransformer()) .getResultList(); }
方法2:创建DTO实体类映射结果
定义与SQL查询字段顺序完全一致的DTO类,Spring Data JPA会按类字段顺序序列化JSON,同时明确数据类型。
- 定义DTO类(字段顺序严格匹配SQL列顺序):
import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigDecimal; import java.time.LocalDateTime; public class FineReportDTO { private String NIT; private String númeroComparendo; private String placa; private String códigoInfracción; private LocalDateTime fechaInfracción; private BigDecimal valor; private String ciudad; private String dirección; private LocalDateTime fechaDelCargue; private String estadoNotificación; private String estadoProceso; private String origen; private String estadoPago; private LocalDateTime fechaPago; // 构造方法参数顺序必须和SQL查询列顺序完全一致 public FineReportDTO(String NIT, String númeroComparendo, String placa, String códigoInfracción, LocalDateTime fechaInfracción, BigDecimal valor, String ciudad, String dirección, LocalDateTime fechaDelCargue, String estadoNotificación, String estadoProceso, String origen, String estadoPago, LocalDateTime fechaPago) { this.NIT = NIT; this.númeroComparendo = númeroComparendo; this.placa = placa; this.códigoInfracción = códigoInfracción; this.fechaInfracción = fechaInfracción; this.valor = valor; this.ciudad = ciudad; this.dirección = dirección; this.fechaDelCargue = fechaDelCargue; this.estadoNotificación = estadoNotificación; this.estadoProceso = estadoProceso; this.origen = origen; this.estadoPago = estadoPago; this.fechaPago = fechaPago; } // Getter方法,用@JsonProperty指定JSON键名 public String getNIT() { return NIT; } @JsonProperty("Número Comparendo") public String getNúmeroComparendo() { return númeroComparendo; } @JsonProperty("Placa") public String getPlaca() { return placa; } @JsonProperty("Código Infracción") public String getCódigoInfracción() { return códigoInfracción; } @JsonProperty("Fecha Infracción") public LocalDateTime getFechaInfracción() { return fechaInfracción; } @JsonProperty("Valor") public BigDecimal getValor() { return valor; } @JsonProperty("Ciudad") public String getCiudad() { return ciudad; } @JsonProperty("Dirección") public String getDirección() { return dirección; } @JsonProperty("Fecha del cargue") public LocalDateTime getFechaDelCargue() { return fechaDelCargue; } @JsonProperty("Estado Notificación") public String getEstadoNotificación() { return estadoNotificación; } @JsonProperty("Estado Proceso") public String getEstadoProceso() { return estadoProceso; } @JsonProperty("Origen") public String getOrigen() { return origen; } @JsonProperty("Estado Pago") public String getEstadoPago() { return estadoPago; } @JsonProperty("Fecha Pago") public LocalDateTime getFechaPago() { return fechaPago; } }
- 修改查询方法返回DTO列表:
@Query(value = "select f.entity_id as \"NIT\", f.subpoena_number as \"Número Comparendo\", f.plate as \"Placa\", f.code as \"Código Infracción\", f.infringement_date \"Fecha Infracción\", f.amount as \"Valor\", f.city as \"Ciudad\", f.address as \"Dirección\", f.upload_date as \"Fecha del cargue\", f.notification_status as \"Estado Notificación\", f.process_status as \"Estado Proceso\", f.source_data as \"Origen\", case when f.paying = true then 'PAGADO' else 'SIN PAGAR' end as \"Estado Pago\", f.payment_date as \"Fecha Pago\" from renting.fine f inner join renting.company c on c.nit = f.entity_id and c.companyparent_id = f.companyparent inner join renting.notification n on f.id = n.fine_id where f.companyparent = :companyParent and f.upload_date > :lastUpdate and f.upload_date < :endUpdate ", nativeQuery = true) List<FineReportDTO> generateComparingReport(@Param("lastUpdate") LocalDateTime lastUpdate, @Param("endUpdate") LocalDateTime endUpdate, @Param("companyParent") String companyParent);
这种方式既能严格保留顺序,又能避免Map中Object类型的转换问题,序列化JSON时直接按类字段顺序输出。
方法3:手动重构有序Map
如果不想修改查询逻辑,可以在获取原始List<Map<String, Object>>后,按预定义字段顺序重新构建LinkedHashMap。
- 定义固定字段顺序列表:
private static final List<String> FIELD_ORDER = Arrays.asList( "NIT", "Número Comparendo", "Placa", "Código Infracción", "Fecha Infracción", "Valor", "Ciudad", "Dirección", "Fecha del cargue", "Estado Notificación", "Estado Proceso", "Origen", "Estado Pago", "Fecha Pago" );
- 转换结果:
List<Map<String, Object>> originalResult = generateComparingReport(lastUpdate, endUpdate, companyParent); List<Map<String, Object>> orderedResult = originalResult.stream() .map(originalMap -> { Map<String, Object> orderedMap = new LinkedHashMap<>(); FIELD_ORDER.forEach(field -> orderedMap.put(field, originalMap.get(field))); return orderedMap; }) .collect(Collectors.toList()); // 将orderedResult序列化为JSON即可
内容的提问来源于stack exchange,提问作者Carlos Daniel Ospina Salazar
相关产品推荐
相关产品推荐

