返回

解决 Spring Data JPA 找不到列名问题

java

解决 "Unable to find column position by name" 问题

碰上 "Unable to find column position by name: xxx [The column name xxx was not found in this ResultSet.]" 错误,看着就头疼。 别急,咱们来一步步解决它。

问题出在哪?

问题说白了就是:你的 Java 代码试图从数据库查询结果里拿一个不存在的字段。虽然你觉得你的 Entity 映射是对的,你的查询语句也只选择了需要的列,但 Spring Data JPA 在把查询结果转换成 compras 对象的时候,还是会去尝试获取 Entity 里定义的所有字段, 结果就报错了。 就算你不取某些字段, 也是要面对这个问题的.

怎么解决?

几种方法,总有一款适合你:

1. DTO (Data Transfer Object) 大法

  • 原理: DTO 就是一个简单的 Java 类,专门用来存放你要的数据。它只包含你查询语句里真正需要的字段,跟你的数据库表结构没必要完全一样。

  • 好处: 干净利落,避免了不必要的字段映射。而且,如果你以后查询需求变了,只需要修改 DTO 就行,不用动你的 Entity。

  • 怎么做:

    1. 创建一个 DTO 类:

      //ComprasDTO.java
      public class ComprasDTO {
          private Long id;
          private Date fechaEmision;
          private String numeroSerie;
          private String numeroCorrelativo;
          private BigDecimal valor;
           private BigDecimal igv; // Assuming IGV exists
          private BigDecimal icbp;
          private BigDecimal isc;
          private BigDecimal otrosCargos;
         // 添加对应的 getter 和 setter
        public ComprasDTO(Long id, Date fechaEmision, String numeroSerie, String numeroCorrelativo, BigDecimal valor, BigDecimal igv, BigDecimal icbp, BigDecimal isc, BigDecimal otrosCargos) {
      this.id = id;
      this.fechaEmision = fechaEmision;
      this.numeroSerie = numeroSerie;
      this.numeroCorrelativo = numeroCorrelativo;
      this.valor = valor;
      this.igv = igv;
      this.icbp = icbp;
      this.isc = isc;
      this.otrosCargos = otrosCargos;
      

    }

        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Date getFechaEmision() {
            return fechaEmision;
        }
    
        public void setFechaEmision(Date fechaEmision) {
            this.fechaEmision = fechaEmision;
        }
    
        public String getNumeroSerie() {
            return numeroSerie;
        }
    
        public void setNumeroSerie(String numeroSerie) {
            this.numeroSerie = numeroSerie;
        }
    
        public String getNumeroCorrelativo() {
            return numeroCorrelativo;
        }
    
        public void setNumeroCorrelativo(String numeroCorrelativo) {
            this.numeroCorrelativo = numeroCorrelativo;
        }
    
        public BigDecimal getValor() {
            return valor;
        }
    
        public void setValor(BigDecimal valor) {
            this.valor = valor;
        }
    
        public BigDecimal getIcbp() {
            return icbp;
        }
        public void setIcbp(BigDecimal icbp)
    { this.icbp =icbp;}
    
      public BigDecimal getIgv() {
        return igv;
    }
    
     public void setIgv(BigDecimal igv) {
       this.igv = igv;
    }
    
      public BigDecimal getIsc() {
           return isc;
    }
    
    public void setIsc(BigDecimal isc) {
      this.isc = isc;
     }
    
    public BigDecimal getOtrosCargos() {
      return otrosCargos;
    }
        public void  setOtrosCargos(BigDecimal otrosCargos){
            this.otrosCargos = otrosCargos;
        }
    
    }
    ```
    
    1. 修改你的 Repository:

      public interface ComprasRepo extends JpaRepository<compras, Long> {
          List<compras> findAllByEntityId(int entityId);
      
          @Query(value = "SELECT id,fecha_emision AS FechaEmision, numero_serie AS NumeroSerie, numero_correlativo AS NumeroCorreltivo, valor AS Valor, igv as IGV, icbp AS ICBP, isc AS ISC, otros_cargos AS OtrosCargos FROM acc._8 WHERE entity_id = :entity AND periodo_tributario = :periodo", nativeQuery = true)
          List<ComprasDTO> TablaPersonal(@Param("entity") Long entityId, @Param("periodo") Integer periodo);
      }
      
    2. 修改测试类

      @SpringBootTest
      class DataWarehouseApplicationTests {
          @Autowired
          private ComprasRepo comprasRepo;
         @Test
         void contextLoads() {
    
             List<ComprasDTO> resultado=comprasRepo.TablaPersonal(1L, 202201);
              for (ComprasDTO record : resultado){
                 System.out.println(record.getFechaEmision()); //打印以测试
                 };
           }
        }
    
  • 进阶
    使用构造函数表达式: 你可以稍微改造一下你的 @Query, 让 JPA 直接帮你把结果映射到 DTO:

         @Query(value = "SELECT new com.yourpackage.ComprasDTO(c.id,c.fechaEmision, c.numeroSerie, c.numeroCorrelativo, c.valor, c.igv, c.icbp, c.isc, c.otrosCargos) FROM compras c WHERE c.entityId = :entity AND c.periodoTributario = :periodo") // 这里不是 native query
            List<ComprasDTO> TablaPersonal(@Param("entity") Long entityId, @Param("periodo") Integer periodo);
    
    

    注意, 这里使用的不是native Query

2. 使用 @SqlResultSetMappingConstructorResult

  • 原理: 这种方式允许你自定义查询结果和 Java 对象之间的映射关系。你需要告诉 JPA 怎么把每一列的数据对应到你的对象的哪个字段。

  • 怎么做:

    1. 在你的 compras Entity 类里定义映射:
        @Entity
        @Table(name = "_8", schema = "acc")
        @AllArgsConstructor
        @NoArgsConstructor
        @Getter
        @Setter
         @SqlResultSetMapping(
             name = "ComprasMapping",
             classes = @ConstructorResult(
              targetClass = ComprasDTO.class,
                 columns = {
                @ColumnResult(name = "id", type = Long.class),
                @ColumnResult(name = "FechaEmision", type = Date.class),
                @ColumnResult(name = "NumeroSerie", type = String.class),
                @ColumnResult(name = "NumeroCorreltivo", type = String.class),
                @ColumnResult(name = "Valor", type = BigDecimal.class),
                 @ColumnResult(name = "IGV", type = BigDecimal.class),
                @ColumnResult(name = "ICBP", type = BigDecimal.class),
                @ColumnResult(name = "ISC", type = BigDecimal.class),
                @ColumnResult(name = "OtrosCargos", type = BigDecimal.class)
      }))
      
       public class compras {
         // ... 你的 Entity 代码 ...
       }
      
    2. 修改Repository
    public interface ComprasRepo extends JpaRepository<compras, Long> {
    
        List<compras> findAllByEntityId(int entityId);
       @Query(value = "SELECT id,fecha_emision AS FechaEmision, numero_serie AS NumeroSerie, numero_correlativo AS NumeroCorreltivo, valor AS Valor, igv as IGV, icbp AS ICBP, isc AS ISC, otros_cargos AS OtrosCargos FROM acc._8 WHERE entity_id = :entity AND periodo_tributario = :periodo",
         nativeQuery = true, name= "ComprasMapping")
       List<ComprasDTO> TablaPersonal(@Param("entity") Long entityId, @Param("periodo") Integer periodo);
       }
    
    1. 测试代码和DTO代码,同方法1一样。
  • 额外说明: 这种方法稍微复杂一点,但如果你的映射关系比较特殊,或者你需要更细粒度的控制,它就很有用了。

3. 使用@NamedNativeQuery (类似方法2,但配置位置不同).

  1. 在 Entity 中增加定义:
    ```java
@Entity
@Table(name = "_8", schema = "acc")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@NamedNativeQuery(name = "compras.TablaPersonal",
query = "SELECT id,fecha_emision AS FechaEmision, numero_serie AS NumeroSerie, numero_correlativo AS NumeroCorreltivo, valor AS Valor, igv as IGV, icbp AS ICBP, isc AS ISC, otros_cargos AS OtrosCargos FROM acc._8 WHERE entity_id = :entity AND periodo_tributario = :periodo",
resultSetMapping = "ComprasMapping")

@SqlResultSetMapping(
name = "ComprasMapping",
classes = @ConstructorResult(
targetClass = ComprasDTO.class,
columns = {
@ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "FechaEmision", type = Date.class),
@ColumnResult(name = "NumeroSerie", type = String.class),
@ColumnResult(name = "NumeroCorreltivo", type = String.class),
@ColumnResult(name = "Valor", type = BigDecimal.class),
@ColumnResult(name = "IGV", type = BigDecimal.class),
@ColumnResult(name = "ICBP", type = BigDecimal.class),
@ColumnResult(name = "ISC", type = BigDecimal.class),
@ColumnResult(name = "OtrosCargos", type = BigDecimal.class)
}))
public class compras {
//...
}

```
2. 修改 Repository 接口,使用`compras.TablaPersonal`做关联:

```java
  public interface ComprasRepo extends JpaRepository<compras, Long> {

      List<compras> findAllByEntityId(int entityId);
    @Query(nativeQuery = true) // 注意,这里不需要 value 了.
    List<ComprasDTO> TablaPersonal(@Param("entity") Long entityId, @Param("periodo") Integer periodo);

  }
```
  1. 测试代码和DTO代码,同方法1一样。

4. (不推荐,但可以了解)修改查询,选择所有字段

  • 原理: 最偷懒的方法,直接把你 Entity 里所有字段都查出来。
  • 坏处: 浪费资源,降低效率。万一以后你表结构变了,还得改代码。

安全建议

无论你用哪种方法, 要记住:

  • 参数化查询: 永远使用参数化查询 (像 :entity:periodo 这样),不要直接把变量拼接到 SQL 语句里。这可以防止 SQL 注入攻击。
  • 最小权限原则: 给你的数据库用户分配最小的必要权限。比如,如果你的程序只需要读取数据,就不要给它写数据的权限。

通过上面几种方法,应该就能搞定这个 “找不到列” 的问题了。记住,写代码的时候多考虑一下数据的流向,尽量保持代码的整洁和高效。