Я использую 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
Если вы используете ключевое слово new, то указанный класс может быть либо каким-либо классом DTO (т. е. не сопоставленным как сущность), либо существующим классом Entity. Однако в последнем случае возвращаемый экземпляр будет находиться в состоянии НОВЫЙ, т.е. не будет управляться.
Где E — сущность
select E from E where ...
->
вернуть управляемый экземпляр. Лениво отображаемые ассоциации могут быть получены по требованию. Изменения, внесенные в контексте сеанса, будут автоматически сохранены по завершении транзакции.
select new E(...)
->
возвращает неуправляемый экземпляр E. Будут установлены только поля, указанные в выражении конструктора.
select new SomeDto(...)
->
вернуть какой-либо другой класс, заполненный в соответствии с выражением конструктора.
При использовании нового синтаксиса у вас должен быть конструктор, соответствующий полям запроса. В вашем OneNew нет конструктора с двумя аргументами, поэтому появляется сообщение об ошибке.
Таким образом, эти две вещи совершенно разные, и варианты использования будут определять, какую из них вы хотите использовать.
Итак, первым способом, без new, OneNew управляется спящим режимом, и изменения в нем будут отражаться в базе данных, даже если я не аннотировал его @Entity и @Id?
Спасибо за ответ, я не получаю эту ошибку с кодом, который я упомянул в ответе, на самом деле оба способа работают нормально, даже если тип возвращаемого значения OneNew не аннотирован аннотацией Entity. Я использую аннотацию lombok AllArgsConstructor, которая уже генерирует конструктор с двумя аргументами, необходимый для запроса. Исключения, которые я добавил, возвращаются, когда я намеренно не сопоставляю аргументы в конструкторе, чтобы увидеть, какое исключение оба способа возвращают как разницу.