提问者:小点点

Spring——在存在“@Query”注释的情况下,通过接口方法顺序而不是名称来解决JPA投影?


我的理解是,在Spring JPA中创建投影时,字段会按名称解析。

使用@Query注释时似乎并非如此。

这是预期的行为还是我的查询有问题?

假设我有一个实体

@Entity
@Table(name = "foo")
public class Foo {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Integer id;

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

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

    ... much more that we are not interested in ...
}

我们只想提取idnamecode属性。所以让我们编写一个简单的投影界面:

@Repository
public interface FooRepository extends JpaRepository<Foo, Integer> {
    FooTestProjection findOneByCode(String code);

    interface FooTestProjection {
        Integer getId();

        String getName();

        String getCode();
    }
}

以及验证我们正在做什么的测试:

@Test
void fooTestProjections() {
    Foo targetFoo = repository.saveAndFlush(testData.getFoo());

    getEntityManager().clear();

    FooTestProjection testProjection = repository.findOneByCode(targetFoo.getCode());

    assertThat(testProjection.getId()).isEqualTo(targetFoo.getId());
    assertThat(testProjection.getName()).isEqualTo(targetFoo.getName());
    assertThat(testProjection.getCode()).isEqualTo(targetFoo.getCode());

}

作品

这里的期望是属性按名称解析。所以让我们在投影界面中切换getCode()getName()

interface FooTestProjection {
    Integer getId();

    String getCode();

    String getName();
}

是的,仍然有效。

现在,假设我们想要所有Foo的投影,并且我们想要使用@Query来做到这一点。

(动机:我们最终想要构造一个包含不相关表属性的投影,所以我们使用@QueryLEFT JOIN它们。)

所以让我们调整我们的存储库方法:

@Repository
public interface FooRepository extends JpaRepository<Foo, Integer> {
    @Query("SELECT" +
        " f.id AS fooId," +
        " f.name AS fooName," +
        " f.code AS fooCode" +
        " FROM Foo  f"
    )
    List<FooTestProjection> findProjections();

    interface FooTestProjection {
        Integer getFooId();

        String getFooName();

        String getFooCode();
    }
}

和我们的测试:

@Test
void fooTestProjections() {
    Foo targetFoo = repository.saveAndFlush(testData.getFoo());

    getEntityManager().clear();

    FooTestProjection testProjection = repository.findProjections().get(0);

    assertThat(testProjection.getFooId()).isEqualTo(targetFoo.getId());
    assertThat(testProjection.getFooName()).isEqualTo(targetFoo.getName());
    assertThat(testProjection.getFooCode()).isEqualTo(targetFoo.getCode());
}

作品

测试绿色,一切都好,不是吗?

没有。

我们在这里的期望是Spring会查看我们构建的结果集,并通过我们定义的列别名将它们与投影接口匹配。

事实并非如此。

让我们在我们的界面中切换getFOCode()getFOName(),而不是在我们的查询中:

@Query("SELECT" +
    " f.id AS fooId," +
    " f.name AS fooName," +
    " f.code AS fooCode" +
    " FROM Foo  f"
)
List<FooTestProjection> findProjections();

interface FooTestProjection {
    Integer getFooId();

    String getFooCode();

    String getFooName();
}

失败

为什么会失败?因为现在,getFOCode()返回target etFoonamegetfoName()返回target etFoocode

因此,如果我们切换选择参数以再次匹配接口顺序…

@Query("SELECT" +
    " f.id AS fooId," +
    " f.code AS fooCode," +
    " f.name AS fooName" +
    " FROM Foo  f"
)
List<FooTestProjection> findProjections();

interface FooTestProjection {
    Integer getFooId();

    String getFooCode();

    String getFooName();
}

这再次工作。

我发现这非常违反直觉,以至于我质疑我的查询的有效性。

如果有人能对这种行为有所了解,我将不胜感激。


共1个答案

匿名用户

最新版本的Hibernate(5.2.1起)修复了使用投影时提到的排序问题。对于旧的Hibernate版本,将原生查询中的列/别名按字母顺序保留,它应该可以正常工作。

您可能想检查/故障排除下面的方法了解详细信息

org.springframework.data.repository.query.ResultProcessor.ProjectingConverter.toMap(Collection<?>, List<String>)