Spring Data JPA/Hibernate: выражения конструктора JPQL - разница между использованием «нового (..)» и его неиспользованием

Я использую JPQL и проецирую определенные возвращаемые поля в класс, отличный от Entity. Я могу добиться этого, используя как минимум следующие два способа в JPQL с EntityManger (em в коде): -

@Test
void testWithoutNew() {
    List<OneNew> l = em.createQuery("SELECT o.field, f.field FROM One o JOIN o.manyForeignKey f", OneNew.class).getResultList();
    System.out.println(l);
    
}
@Test
void testWithNew() {
    List<OneNew> l2 = em.createQuery("SELECT new com.test.OneNew(o.field, f.field) FROM One o JOIN o.manyForeignKey f", OneNew.class).getResultList();
    System.out.println(l2);
}

Какой из этих способов предпочтительнее и в чем разница между ними?

Каков предполагаемый способ достижения этой функциональности? И когда вам следует предпочесть один подход другому?

Класс, на который я проецирую результаты:

@ToString
@AllArgsConstructor
class OneNew {
    private String field;
    private String joinedTablesField;
}

И мои сущности: -

@Entity
@Getter
@Setter
@ToString
public class One {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long oneId;
    
    private String field;
    private String fieldNotInProjection;
    
    @OneToMany
    @JoinColumn(name = "one_id")
    private List<ManyForeignKey> manyForeignKey;
    
}
@Entity
@Getter
@Setter
@ToString
public class ManyForeignKey {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long manyForeignKeyId;
    
    private String field;
    private String fieldNotInProjection;

}

Оба вышеуказанных способа работают, но когда я пытаюсь намеренно не сопоставить аргументы в конструкторе с полями, возвращаемыми в запросе, я обнаруживаю, что оба этих способа выдают разные исключения: -

если не используется синтаксис new(..): -

org.hibernate.query.QueryTypeMismatchException: Result type is 'OneNew' but the query returned a 'String'
    at org.hibernate.sql.results.internal.RowTransformerCheckingImpl.transformRow(RowTransformerCheckingImpl.java:33)
    at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:102)
    at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:205)
    at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
    at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:211)
    at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83)
    at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76)
    at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65)
    at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$2(ConcreteSqmSelectQueryPlan.java:139)
    at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:382)
    at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:302)
    at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:526)
    at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:423)
    at org.hibernate.query.Query.getResultList(Query.java:120)
    at com.test.TestDbRelationshipsApplicationTests.testWithoutNew(TestDbRelationshipsApplicationTests.java:49)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

В то время как при использовании синтаксиса new(..): -

java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Missing constructor for type 'OneNew' [SELECT new com.test.OneNew(o.field, f.field) FROM One o JOIN o.manyForeignKey f]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:860)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:140)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:319)
    at jdk.proxy2/jdk.proxy2.$Proxy106.createQuery(Unknown Source)
    at com.test.TestDbRelationshipsApplicationTests.testWithNew(TestDbRelationshipsApplicationTests.java:57)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.hibernate.query.SemanticException: Missing constructor for type 'OneNew' [SELECT new com.test.OneNew(o.field, f.field) FROM One o JOIN o.manyForeignKey f]
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInstantiation(SemanticQueryBuilder.java:1506)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInstantiation(SemanticQueryBuilder.java:275)
    at org.hibernate.grammars.hql.HqlParser$InstantiationContext.accept(HqlParser.java:4029)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectableNode(SemanticQueryBuilder.java:1453)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelection(SemanticQueryBuilder.java:1407)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectClause(SemanticQueryBuilder.java:1400)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1249)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1035)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:275)
    at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2132)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1020)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:275)
    at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2003)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:490)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:449)
    at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:322)
    at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71)
    at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145)
    at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132)
    at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:802)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:852)
    ... 11 more
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
110
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы используете ключевое слово new, то указанный класс может быть либо каким-либо классом DTO (т. е. не сопоставленным как сущность), либо существующим классом Entity. Однако в последнем случае возвращаемый экземпляр будет находиться в состоянии НОВЫЙ, т.е. не будет управляться.

Где E — сущность

select E from E where ... ->

вернуть управляемый экземпляр. Лениво отображаемые ассоциации могут быть получены по требованию. Изменения, внесенные в контексте сеанса, будут автоматически сохранены по завершении транзакции.

select new E(...) ->

возвращает неуправляемый экземпляр E. Будут установлены только поля, указанные в выражении конструктора.

select new SomeDto(...) ->

вернуть какой-либо другой класс, заполненный в соответствии с выражением конструктора.

При использовании нового синтаксиса у вас должен быть конструктор, соответствующий полям запроса. В вашем OneNew нет конструктора с двумя аргументами, поэтому появляется сообщение об ошибке.

Таким образом, эти две вещи совершенно разные, и варианты использования будут определять, какую из них вы хотите использовать.

Спасибо за ответ, я не получаю эту ошибку с кодом, который я упомянул в ответе, на самом деле оба способа работают нормально, даже если тип возвращаемого значения OneNew не аннотирован аннотацией Entity. Я использую аннотацию lombok AllArgsConstructor, которая уже генерирует конструктор с двумя аргументами, необходимый для запроса. Исключения, которые я добавил, возвращаются, когда я намеренно не сопоставляю аргументы в конструкторе, чтобы увидеть, какое исключение оба способа возвращают как разницу.

Sidharth Bajpai 24.06.2024 17:24

Итак, первым способом, без new, OneNew управляется спящим режимом, и изменения в нем будут отражаться в базе данных, даже если я не аннотировал его @Entity и @Id?

Sidharth Bajpai 24.06.2024 17:24

Другие вопросы по теме