解决 Spring Data JPA 找不到列名问题
2025-03-08 20:58:35
解决 "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。
-
怎么做:
-
创建一个 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; } } ```
-
修改你的 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); }
-
修改测试类
@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. 使用 @SqlResultSetMapping
和 ConstructorResult
-
原理: 这种方式允许你自定义查询结果和 Java 对象之间的映射关系。你需要告诉 JPA 怎么把每一列的数据对应到你的对象的哪个字段。
-
怎么做:
- 在你的
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 代码 ... }
- 修改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); }
- 测试代码和DTO代码,同方法1一样。
- 在你的
-
额外说明: 这种方法稍微复杂一点,但如果你的映射关系比较特殊,或者你需要更细粒度的控制,它就很有用了。
3. 使用@NamedNativeQuery
(类似方法2,但配置位置不同).
- 在 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);
}
```
- 测试代码和DTO代码,同方法1一样。
4. (不推荐,但可以了解)修改查询,选择所有字段
- 原理: 最偷懒的方法,直接把你 Entity 里所有字段都查出来。
- 坏处: 浪费资源,降低效率。万一以后你表结构变了,还得改代码。
安全建议
无论你用哪种方法, 要记住:
- 参数化查询: 永远使用参数化查询 (像
:entity
和:periodo
这样),不要直接把变量拼接到 SQL 语句里。这可以防止 SQL 注入攻击。 - 最小权限原则: 给你的数据库用户分配最小的必要权限。比如,如果你的程序只需要读取数据,就不要给它写数据的权限。
通过上面几种方法,应该就能搞定这个 “找不到列” 的问题了。记住,写代码的时候多考虑一下数据的流向,尽量保持代码的整洁和高效。