解决 Spring JPA 异常:“Unable to Locate Attribute with the Given Name”

1、简介

Spring 为程序员简化 Java 应用程序中的数据库交互提供了一个最强大的框架,那就是 Spring JPA(Java Persistence API)。它为 JPA 提供了一个稳定的抽象。

然而,尽管使用方便,开发人员还是经常会遇到一些错误,而这些错误的排查和解决都非常具有迷惑性。其中一个常见问题就是 “Unable to Locate Attribute with the Given Name” 错误。

本文将带你了解 Spring JPA 出现 “Unable to Locate Attribute with the Given Name” 异常的原因以及解决办法。

2、案例

我们生产了一个可穿戴的小工具。经过最近的一项调查,我们的营销团队发现,在我们的平台上按传感器类型、价格和受欢迎程度对产品进行分类,可以突出最受欢迎的产品,从而帮助客户做出更好的购买决策。

可穿戴设备的查询决策示意图

3、添加 Maven 依赖

我们使用 H2 内存数据库在项目中创建一个可穿戴设备表,并将样本数据填充到该表中,以便在接下来的测试中使用。

首先,添加以下 Maven 依赖

<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId> 
    <version>2.2.224</version> 
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.7.11</version>
</dependency>

4、应用配置

src/main/resources 文件夹中,创建包含以下配置内容的 application-h2.properties

# H2 配置
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=create-drop

# Spring Datasource URL
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1

src/main/resources 文件夹中创建名为 testdata.sql 的 SQL 文件,其内容如下,用于在 H2 数据库中创建 wearables 表,其中包含一些预定义条目:

CREATE TABLE IF NOT EXISTS wearables (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    price DECIMAL(10, 2),
    sensor_type VARCHAR(255),
    popularity_index INT
);

DELETE FROM wearables;

INSERT INTO wearables (id, name, price, sensor_type, popularity_index)
VALUES (1, 'SensaWatch', '500.00', 'Accelerometer', 5);

INSERT INTO wearables (id, name, price, sensor_type, popularity_index)
VALUES (2, 'SensaBelt', '300.00', 'Heart Rate', 3);

INSERT INTO wearables (id, name, price, sensor_type, popularity_index)
VALUES (3, 'SensaTag', '120.00', 'Proximity', 2);

INSERT INTO wearables (id, name, price, sensor_type, popularity_index)
VALUES (4, 'SensaShirt', '150.00', 'Human Activity Recognition', 2);

5、Model 定义

定义 WearableEntity 实体:

@Entity 
public class WearableEntity { 

    @Id @GeneratedValue 
    private Long Id; 
    
    @Column(name = "name") 
    private String Name; 

    @Column(name = "price") 
    private BigDecimal Price; 

    // 例如,“Heart Rate Monitor(心率监测)”、“Neuro Feedback(神经反馈)” 等。 
    @Column(name = "sensor_type") 
    private String SensorType; 
    
    @Column(name = "popularity_index") 
    private Integer PopularityIndex; 

}

注意,实体中的字段名称是大写字母开头

6、定义实体过滤查询

实体定义好后,在数据库中添加一个查询,根据过滤条件检索 WearableEntity

public interface WearableRepository extends JpaRepository<WearableEntity, Long> {
    List<WearableEntity> findAllByOrderByPriceAscSensorTypeAscPopularityIndexDesc();
}

解释如下:

  1. findAllBy:该方法检索所有属于 WearableEntityWearableEntity 类型的记录。
  2. OrderByPriceAsc:按价格(price)升序对结果排序。
  3. SensorTypeAsc:按价格排序后,再按传感器类型(sensorType)升序排序。
  4. PopularityIndexDesc:最后,按照 popularityIndex 从高到低对结果排序(因为受欢迎程度越高越好)

7、通过集成测试来测试 Repository

现在,在项目中引入集成测试来测试 WearableRepository 的行为:

public class WearableRepositoryIntegrationTest { 
    @Autowired 
    private WearableRepository wearableRepository; 

    @Test 
    public void testFindByCriteria()  {
        assertThat(wearableRepository.findAllByOrderByPriceAscSensorTypeAscPopularityIndexDesc()) .hasSize(4);
    }
}

8、运行集成测试

在运行集成测试时,我们会发现它无法加载 Application Context,并出现以下错误:

Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute  with the the given name [price] on this ManagedType [com.baeldung.spring.data.jpa.filtering.WearableEntity]

9、根本原因

Hibernate 使用命名约定将字段映射到数据库列。假设实体类中的字段名与相应的列名或预期约定不一致。在这种情况下,Hibernate 将无法映射它们,从而在执行查询或 Schema 验证时导致异常。

在本例中:

  • Hibernate 期望使用 namepricepopularityIndex(驼峰风格)等字段名称,但实体却错误地使用了 IdNameSensorTypePricePopularityIndex(首字母大写)等字段名称。
  • 在执行 findAllByOrderByPriceAsc() 这样的查询时,Hibernate 会尝试将 SQL 价格列映射到实体字段。由于该字段名为 Price(大写字母 “P”),因此无法找到属性,导致出现 IllegalArgumentException 异常。

10、修改实体,解决异常

现在,将 WearableEntity 类中字段的命名从 “首字母大写” 改为 “首字母小写的驼峰” 格式:

@Entity
@Table(name = "wearables")
public class WearableValidEntity {
    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private BigDecimal price;

    @Column(name = "sensor_type")
    private String sensorType;

    @Column(name = "popularity_index")
    private Integer popularityIndex;
}

修改完毕后,重新运行 WearableRepositoryIntegrationTest。测试通过!

11、总结

本文介绍了Spring JPA 出现 “Unable to Locate Attribute with the Given Name” 异常的原因以及解决办法。强调了遵循 JPA 命名约定和最佳实践的重要性,这有助于避免字段映射问题并优化应用性能。


Ref:https://www.baeldung.com/spring-jpa-troubleshooting-attribute-naming-issues