Насколько я понимаю, при создании проекции в Spring JPA поля разрешаются по имени.
Похоже, что это не так при использовании @Query
аннотаций.
Isэто ожидаемое поведение или что-то не так с моим запросом?
Допустим, у меня есть сущность
@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 будет смотреть на набор результатов, который мы создаем, и сопоставлять ихк интерфейсу проекции через псевдоним столбца, который мы определяем.
Это не так.
Давайте переключим getFooCode()
и getFooName()
в нашем интерфейсе, , но не в нашемquery :
@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();
}
FAILS
Почему происходит сбой? Поскольку сейчас , getFooCode()
возвращает name
targetFoo
, а getfooName()
возвращает code
targetFoo
.
Так что если мы переключим выбораргументы, чтобы снова соответствовать порядку интерфейса ...
@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();
}
Это РАБОТАЕТ снова.
Я нахожу это чрезвычайно нелогичным до того, что я подвергаю сомнениюправильность моего запроса.
Буду признателен, если кто-нибудь сможет пролить свет на это поведение.