简体   繁体   English

使用LEFT JOIN和GROUP BY的JPQL查询

[英]JPQL query with LEFT JOIN and GROUP BY

I'm learning JPQL using simple Hibernate 5.0.4, Spring 4.2.3, Maven 3.3.3 based project on Oracle 11g XE. 我正在使用基于简单的Hibernate 5.0.4,Spring 4.2.3,基于Oracle 11g XE的Maven 3.3.3的项目来学习JPQL。 Full source code can be found on my GitHub branch . 完整的源代码可以在我的GitHub分支找到

I've got 2 models: 我有2个型号:

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "T_OWNER")
@NoArgsConstructor
public @Data class OwnerModel {

    public OwnerModel(String firstName, String lastName, Integer age, OwnerType type) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.type = type;
        this.since = new Date(System.currentTimeMillis());
        this.age = age;
    }

    @Id
    @GeneratedValue(generator = "owner-sequence-generator", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "owner-sequence-generator", sequenceName = "OWNER_SEQ", initialValue = 1, allocationSize = 20)
    private Long id;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    @Column(name = "TYPE")
    @Enumerated(EnumType.STRING)
    private OwnerType type;

    @Column(name = "SINCE")
    @Temporal(TemporalType.TIME)
    private Date since;

    @Column(name = "AGE")
    private Integer age;

    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = CarModel.class)
    private List<CarModel> cars = new LinkedList<>();

    public void addCar(CarModel car) {
        cars.add(car);
        car.setOwner(this);
    }
}

import java.sql.Blob;
import java.sql.Clob;
import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "T_CAR")
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "owner")
public @Data class CarModel {

    public CarModel(String name, Integer wheelsNumber, Clob spec, Blob image) {
        super();
        this.name = name;
        this.wheelsNumber = wheelsNumber;
        this.spec = spec;
        this.image = image;
        this.createdIn = new Date(System.currentTimeMillis());
    }

    @Id
    @GeneratedValue(generator = "car-sequence-generator", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "car-sequence-generator", sequenceName = "CAR_SEQ", initialValue = 1, allocationSize = 20)
    private Long id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "CREATED_IN")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdIn;

    @Column(name = "WHEELS_NUMBER")
    private Integer wheelsNumber;

    @Lob
    @Column(name = "SPEC")
    private Clob spec;

    @Lob
    @Column(name = "IMAGE")
    private Blob image;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = OwnerModel.class)
    @JoinColumn(name = "ID_OWNER")
    private OwnerModel owner;
}

They were used to prepare some data in my DB. 它们被用来在我的数据库中准备一些数据。 When I execute such DAO located JPQL query: 当我执行此类DAO定位的JPQL查询时:

    @Override
public List<?> executeSelectWithGroupBy() {
    return (List<?>) getSession().createQuery("select o, COUNT(c) from OwnerModel o LEFT JOIN o.cars c GROUP BY o").list();
}

I've got error as below : 我有如下错误:

Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not extract ResultSet
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:63)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
    at org.hibernate.loader.Loader.getResultSet(Loader.java:2116)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1899)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1875)
    at org.hibernate.loader.Loader.doQuery(Loader.java:919)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:336)
    at org.hibernate.loader.Loader.doList(Loader.java:2611)
    at org.hibernate.loader.Loader.doList(Loader.java:2594)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2423)
    at org.hibernate.loader.Loader.list(Loader.java:2418)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:501)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:371)
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:226)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1268)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:87)
    at com.pduleba.spring.dao.OwnerDaoImpl.executeSelectWithGroupBy(OwnerDaoImpl.java:57)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
    at com.sun.proxy.$Proxy39.executeSelectWithGroupBy(Unknown Source)
    at com.pduleba.spring.services.OwnerServiceImpl.executeSelectWithGroupBy(OwnerServiceImpl.java:40)
    at com.pduleba.spring.controller.QueryControllerImpl.executeQueries(QueryControllerImpl.java:39)
    at com.pduleba.hibernate.Main.execute(Main.java:50)
    at com.pduleba.hibernate.Main.main(Main.java:27)
Caused by: java.sql.SQLSyntaxErrorException: ORA-00979: not a GROUP BY expression

    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
    at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:886)
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1175)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1296)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613)
    at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3657)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1495)
    at org.apache.commons.dbcp2.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:83)
    at org.apache.commons.dbcp2.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:83)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70)
    ... 32 more

As far as I understand it is caused by error in Hibernate generated SQL : 据我了解,这是由Hibernate生成的SQL错误引起的:

select
    ownermodel0_.id as col_0_0_,
    count(cars1_.id) as col_1_0_,
    ownermodel0_.id as id1_1_,
    ownermodel0_.AGE as AGE2_1_,
    ownermodel0_.FIRST_NAME as FIRST_NAME3_1_,
    ownermodel0_.LAST_NAME as LAST_NAME4_1_,
    ownermodel0_.SINCE as SINCE5_1_,
    ownermodel0_.TYPE as TYPE6_1_ 
from
    hibernate.T_OWNER ownermodel0_ 
left outer join
    hibernate.T_CAR cars1_ 
        on ownermodel0_.id=cars1_.ID_OWNER 
group by
    ownermodel0_.id

where GROUP BY clause should include all columns (not just id). 其中GROUP BY子句应包括所有列(而不仅仅是ID)。 In my opinion hibernate should generate such SQL : 我认为休眠应该生成这样的SQL:

select
    ownermodel0_.id as col_0_0_,
    count(cars1_.id) as col_1_0_,
    ownermodel0_.id as id1_1_,
    ownermodel0_.AGE as AGE2_1_,
    ownermodel0_.FIRST_NAME as FIRST_NAME3_1_,
    ownermodel0_.LAST_NAME as LAST_NAME4_1_,
    ownermodel0_.SINCE as SINCE5_1_,
    ownermodel0_.TYPE as TYPE6_1_ 
from
    hibernate.T_OWNER ownermodel0_ 
left outer join
    hibernate.T_CAR cars1_ 
        on ownermodel0_.id=cars1_.ID_OWNER 
group by
    ownermodel0_.id,
    ownermodel0_.AGE,
    ownermodel0_.FIRST_NAME,
    ownermodel0_.LAST_NAME,
    ownermodel0_.SINCE,
    ownermodel0_.TYPE; 

However (as far as I understand) here and here are shown exactly the same JPQL Queries comparing to mine. 但是,据我所知, 这里这里显示的JPQL查询与我的完全相同。

Is it a bug in Hibernate or just a hidden bug in my code? 它是Hibernate中的错误,还是我的代码中的隐藏错误?

I appreciate your help and advice. 感谢您的帮助和建议。

Let's better do this: 让我们更好地做到这一点:

select 
    o.column1, o.column2, COUNT(c) 

from 
    OwnerModel o 

LEFT JOIN 
    o.cars c 

GROUP BY 
    o.column1, o.column2

because count method destructs all the query notion 因为count方法破坏了所有查询概念

It would not work for hibernate. 它不适用于休眠状态。 As you can see there is issue connected with this in Jira - https://hibernate.atlassian.net/browse/HHH-2436 and it's in unresolved state. 如您所见,在Jira中存在与此相关的问题-https: //hibernate.atlassian.net/browse/HHH-2436 ,它处于未解决状态。

Links that you provided first is JPA spec and second one there is not group by model query but just by simple numeric field. 您首先提供的链接是JPA规范,第二个链接不是按模型查询分组,而仅按简单数字字段分组。

As @GingerHead answered it would be easier to modfiy your query @GingerHead回答后,修改查询会更容易

In all your references grouping is happening on simple fields, and aggregation is happening on simple fields - thus in proposed examples SQL will work. 在所有引用中,分组是在简单字段上发生的,聚合是在简单字段上发生的-因此在建议的示例中,SQL将起作用。

It seems like a problem with your code, you are trying to fetch an EAGER loaded collection, as part of GROUP BY . 您的代码似乎有问题,您正在尝试获取作为GROUP BY一部分的EAGER加载的集合。

Nevertheless your List<CarModel> is always prepopulated (it is marked as EAGER ) - so to get the count simply get the length of your list once your OwnerModel entity is loaded. 但是,您的List<CarModel>始终是预填充的(标记为EAGER )-因此要获取计数,只需在OwnerModel实体加载后获取列表的长度即可。

I would recommend to rework your Model and DAO layer in order to remove @OneToMany List<CarModel> cars field from OwnerModel . 我建议对您的Model和DAO图层进行重做,以便从OwnerModel删除@OneToMany List<CarModel> cars字段。 If you always need the count on available CarModels just add this field to your OwnerModel entity as @Formula expression. 如果您始终需要依靠可用的CarModels只需将此字段作为@Formula表达式添加到OwnerModel实体中@Formula

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM