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

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(按插入顺序保存)。

  1. 自定义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;
    }
}
  1. 手动执行查询并应用转换器:
@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,同时明确数据类型。

  1. 定义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; }
}
  1. 修改查询方法返回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。

  1. 定义固定字段顺序列表:
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"
);
  1. 转换结果:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:11:48