我的理解是,在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 ...
}
我们只想提取id
、name
和code
属性。所以让我们编写一个简单的投影界面:
@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
来做到这一点。
(动机:我们最终想要构造一个包含不相关表属性的投影,所以我们使用@Query
来LEFT 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 etFoo
的name
,getfoName()
返回target etFoo
的code
。
因此,如果我们切换选择参数以再次匹配接口顺序…
@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();
}
这再次工作。
我发现这非常违反直觉,以至于我质疑我的查询的有效性。
如果有人能对这种行为有所了解,我将不胜感激。
最新版本的Hibernate(5.2.1起)修复了使用投影时提到的排序问题。对于旧的Hibernate版本,将原生查询中的列/别名按字母顺序保留,它应该可以正常工作。
您可能想检查/故障排除下面的方法了解详细信息
org.springframework.data.repository.query.ResultProcessor.ProjectingConverter.toMap(Collection<?>, List<String>)