【问题标题】:JPA Criteria multiselect with fetchJPA Criteria multiselect with fetch
【发布时间】:2016-12-22 22:19:20
【问题描述】:

我有以下型号:

@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

}

现在我只想查询列:idnameshortNamementor 引用 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 问题。此外,我没有从 User 到 SampleModel 的反向引用,我不想拥有..

【问题讨论】:

    标签: java hibernate jpa criteria-api


    【解决方案1】:

    我遇到了同样的问题,发现我可以使用以下方法解决它:

    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());
    

    【讨论】:

    • 这对我很有帮助
    【解决方案2】:

    这个问题已经很久没有问过了。但我希望其他一些人能从我的解决方案中受益:

    诀窍是使用子查询。

    假设您的 Application 实体中有申请人(一对一):

    @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());
    

    此代码将为应用程序生成一个选择语句,并为每个应用程序的申请人选择语句。

    请注意,您必须定义与您的多选相对应的适当构造函数。

    【讨论】:

      【解决方案3】:

      我在使用 EclipseLink 作为 JPA 提供程序时遇到了同样的问题:我只想返回映射实体的 id(Gazeciarz 示例中的 «User»)。

      这可以通过替换(在 query.multiselect 子句中)非常简单地实现

      root.get(SampleModel_.mentor)
      

      类似的东西

      root.get(SampleModel_.mentor).get(User_.id)
      

      那么,请求不会返回User的所有字段,而是只返回它的id。

      我也使用了元组查询,但就我而言,这是因为我的查询返回来自多个实体的文件。

      【讨论】:

      • Hibernate 会用这个生成一个cross join。如果表 'SAMPLE_TABLE' 中记录的mentor_id 为空,您将丢失一些数据。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      • 2016-03-09
      • 2020-02-22
      • 2017-08-21
      • 1970-01-01
      • 2013-11-02
      • 2018-05-13
      相关资源
      最近更新 更多