JPA Criteria 多选与提取
JPA Criteria multiselect with fetch
我有以下型号:
@Entity
@Table(name = "SAMPLE_TABLE")
@Audited
public class SampleModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "NAME", nullable = false)
@NotEmpty
private String name;
@Column(name = "SHORT_NAME", nullable = true)
private String shortName;
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
现在我只想查询列:id
、name
、shortName
和 mentor
指的是User
实体(不是完整的实体,因为它有很多其他属性,我希望有最好的性能)。
当我写查询时:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
我有以下异常:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
异常前查询:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
我知道我可以用 join 代替 fetch 但那样我会遇到 N+1 问题。我也没有从用户到 SampleModel 的反向引用,我不想有..
我 运行 遇到了同样的问题,发现我可以使用以下方法解决它:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
而不是
CriteriaQuery<X> crit = builder.createQuery(X.class);
需要做一些额外的工作才能产生最终结果,例如在你的情况下:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
我在使用 EclipseLink 作为 JPA 提供程序时遇到了同样的问题:我只是想 return 映射实体的 ID(在 Gazeciarz 的示例中为“用户”)。
这可以很简单地通过替换(在 query.multiselect 子句中)
来实现
root.get(SampleModel_.mentor)
类似
root.get(SampleModel_.mentor).get(User_.id)
然后,请求将只 return 其 ID,而不是 returning 用户的所有字段。
我也使用了一个元组查询,但就我而言,这是因为我的查询是 return 来自多个实体的文件。
很久没问这个问题了。但我希望其他人能从我的解决方案中受益:
诀窍是使用子查询。
假设您的申请实体中有申请人(一对一):
@Entity
public class Application {
private long id;
private Date date;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
此代码将为申请生成 select 声明,并为每个申请的申请人生成 select 声明。
请注意,您必须定义一个与您的 multiselect 相对应的适当构造函数。
我有以下型号:
@Entity
@Table(name = "SAMPLE_TABLE")
@Audited
public class SampleModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "NAME", nullable = false)
@NotEmpty
private String name;
@Column(name = "SHORT_NAME", nullable = true)
private String shortName;
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
现在我只想查询列:id
、name
、shortName
和 mentor
指的是User
实体(不是完整的实体,因为它有很多其他属性,我希望有最好的性能)。
当我写查询时:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
我有以下异常:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
异常前查询:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
我知道我可以用 join 代替 fetch 但那样我会遇到 N+1 问题。我也没有从用户到 SampleModel 的反向引用,我不想有..
我 运行 遇到了同样的问题,发现我可以使用以下方法解决它:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
而不是
CriteriaQuery<X> crit = builder.createQuery(X.class);
需要做一些额外的工作才能产生最终结果,例如在你的情况下:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
我在使用 EclipseLink 作为 JPA 提供程序时遇到了同样的问题:我只是想 return 映射实体的 ID(在 Gazeciarz 的示例中为“用户”)。
这可以很简单地通过替换(在 query.multiselect 子句中)
来实现root.get(SampleModel_.mentor)
类似
root.get(SampleModel_.mentor).get(User_.id)
然后,请求将只 return 其 ID,而不是 returning 用户的所有字段。
我也使用了一个元组查询,但就我而言,这是因为我的查询是 return 来自多个实体的文件。
很久没问这个问题了。但我希望其他人能从我的解决方案中受益:
诀窍是使用子查询。
假设您的申请实体中有申请人(一对一):
@Entity
public class Application {
private long id;
private Date date;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
此代码将为申请生成 select 声明,并为每个申请的申请人生成 select 声明。
请注意,您必须定义一个与您的 multiselect 相对应的适当构造函数。