Как заставить HIbernate извлекать все свойства корневой сущности и только конкретные свойства связанной сущности?

У меня есть корневая сущность Hostel и его единственная ассоциация User owner,

Когда я получу Hostel сущность мне нужно с нетерпением получить User owner, но только owner3 свойства: userId, firstName, lastName.

На данный момент мой критерий запроса:

Criteria criteria = currenSession().createCriteria(Hostel.class);

criteria.add(Restrictions.ge("endDate", Calendar.getInstance()));
if (StringUtils.notNullAndEmpty(country)) {
        criteria.add(Restrictions.eq("country", country));
}

Long count = (Long) criteria
            .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
            .setProjection(Projections.rowCount()).uniqueResult();

criteria.setFetchMode("owner", FetchMode.SELECT);
criteria.addOrder(Order.desc("rating"));

// needed to reset previous rowCount projection
criteria.setProjection(null);

// retrieve owner association
criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN)
        .setProjection(
                Projections.projectionList()
                        .add(Projections.property("owner.userId"))
                        .add(Projections.property("owner.firstName"))
                        .add(Projections.property("owner.lastName")));

criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

Далее я делаю criteria.list() и я получаю SQL заявление, которое выбирает только owner3 свойства, указанные в списке проекций. Но он не выбирает никаких свойств root Hostel юридическое лицо. Сгенерированный запрос:

select
    owner1_.user_id as y0_,
    owner1_.firstName as y1_,
    owner1_.lastName as y2_ 
from
    HOSTEL this_ 
left outer join
    USER owner1_ 
        on this_.owner_fk=owner1_.user_id 
where
    this_.end_date>=? 
    and this_.country=?        
order by
    this_.rating desc limit ?

Этот запрос не работает, потому что он возвращает пять Mapс которые пусты. Пять карт, потому что есть пять Hostel строки, которые соответствуют условию где. Я создал простой SQL-запрос, и он отлично работает, так что проблема только здесь.

Как заставить hibernate получить все свойства root Hostel сущность и только 3 свойства ассоциированных User owner юридическое лицо?

РЕДАКТИРОВАТЬ Я пытался использовать getSessionFactory().getClassMetadata(Hostel.class) но это дало ошибку при отображении enum в Hostel, Поэтому я отступаю к списку Hostel свойства вручную. На данный момент мой критерий запроса:

// retrieve owner association
        criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN);
        criteria.setProjection(Projections.projectionList()
                .add(Projections.property("hostelId"))
                .add(Projections.property("address"))
                .add(Projections.property("country"))
                .add(Projections.property("region"))
                .add(Projections.property("gender"))
                .add(Projections.property("owner.userId"))
                .add(Projections.property("owner.firstName"))
                .add(Projections.property("owner.lastName")));

List<Hostel> hostels = criteria.list();

for (Hostel hostel : hostels) { // at this line I get error java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.home.hostme.entity.Hostel
            User owner = hostel.getOwner();
            System.out.println("owner=" + owner);
        }

Обратите внимание, что я удалил ALIAS_TO_ENTITY_MAP преобразователь результата. Это сгенерировало такой запрос mysql:

select
    this_.hostel_id as y0_,
    this_.address as y1_,
    this_.country as y2_,
    this_.region as y3_,
    this_.gender as y4_,
    owner1_.user_id as y5_,
    owner1_.firstName as y6_,
    owner1_.lastName as y7_ 
from
    HOSTEL this_ 
left outer join
    USER owner1_ 
        on this_.owner_fk=owner1_.user_id 
where
    this_.end_date>=? 
    and this_.country=? 
order by
    this_.rating desc limit ?

В цикле for-each получается такая ошибка:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.home.hostme.entity.Hostel
    at com.home.hostme.dao.impl.HostelDaoImpl.findHostelBy(HostelDaoImpl.java:168)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy64.findHostelBy(Unknown Source)
    at com.home.hostme.service.HostelService.findHostelBy(HostelService.java:27)
    at com.home.hostme.service.HostelService$$FastClassByCGLIB$$74db5b21.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
    at com.home.hostme.service.HostelService$$EnhancerByCGLIB$$7af3bc10.findHostelBy(<generated>)
    at com.home.hostme.web.hostel.HostelController.doSearch(HostelController.java:94)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

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

List hostels = criteria.list();
        System.out.println("firstRow.class=" + hostels.get(0).getClass());

Это напечатано:

firstRow.class=class [Ljava.lang.Object;

Затем я попытался установить ALIAS_TO_ENTITY_MAP для нового ProjectionList, но результирующий список 'hostels' был:

[{}, {}, {}, {}, {}]

Пять пустых карт. Пять, потому что в db (общежитие таблицы) есть 5 строк, соответствующих предложению where.

Затем я полностью удалил список проекций, и в спящем режиме были найдены 5 хостелов и 5 связанных с ними. User ownerс и ownerИзображения, как и ожидалось.

ПРОБЛЕМА - как остановить спящий режим, связанный с извлечением Image юридическое лицо связанное User owner, Лучше всего было бы выбрать только 3 конкретных реквизита связанных User owner,

Спасибо!

3 ответа

Решение

Вы можете сделать это с помощью прямого запроса:

Query query = session.createQuery("SELECT hostel, owner.id, owner.firstname, "
        +"owner.lastname FROM Hostel hostel LEFT OUTER JOIN hostel.ower AS owner");
List list = query.list();

генерирует SQL как:

выберите hostel0_.id в качестве col_0_0_, user1_.id в качестве col_1_0_, user1_.firstname в качестве col_2_0_, user1_.lastname в качестве col_3_0_, hostel0_.id в качестве id1_0_, hostel0_.name в качестве name2_0_, ..., hostel0__owner_id в качестве пользователя_0 от имени пользователя user_id внешнее присоединение Пользователь user1_ для user1_.id=hostel0_.owner_id

со всеми полями из общежития и только обязательные поля от пользователя.

Список получен с criteria.list() это List<Object[]> чьи строки [ Hostel, Integer, String, String]

Вы можете получить что-то используя Criteria, но Criteria более строгие, чем запросы. Я не мог найти API, который позволяет смешивать объекты и поля. Так что, насколько я знаю, невозможно получить строки, содержащие сущность (общежития) и отдельные поля из ассоциации (owner.userId, owner.firstName, owner.lastName).

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

criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN)
    .setProjection(
            Projections.projectionList()
                    .add(Projections.property("hostelId"))
                    .add(Projections.property("country"))
                    .add(Projections.property("endDate"))
                    ...
                    ... all other properties from Hostel
                    ...
                    .add(Projections.property("owner.userId"))
                    .add(Projections.property("owner.firstName"))
                    .add(Projections.property("owner.lastName")));

Вы можете немного автоматизировать его, используя метаданные (не забывайте идентификатор...) - примечание: я использую проекцию с псевдонимами только для того, чтобы позже можно было использовать класс-оболочку, если вы напрямую используете скалярные значения, вы можете смело опускать Projection.alias:

    ProjectionList hostelProj = Projections.projectionList();
    String id = sessionFactory.getClassMetadata(Hostel.class)
            .getIdentifierPropertyName();
    hostelProperties.add(Projections.alias(Projections.property(id),id));
    for (String prop: sessionFactory.getClassMetadata(Hostel.class).getPropertyNames()) {
        hostelProperties.add(Projections.alias(Projections.property(prop), prop));
    }
    Criteria criteria = session.createCriteria(Hostel.class);
    criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN);
    criteria.setProjection(
            Projections.projectionList()
                    .add(hostelProj)
                    .add(Projections.property("owner.id"))
                    .add(Projections.property("owner.firstName"))
                    .add(Projections.property("owner.lastName")));
    List list = criteria.list();

Этот путь правильно генерирует

выберите this_.id в качестве y0_, this_.name в качестве y1_, ..., this_.user_id в качестве y3_, owner1_.id в качестве y4_, owner1_.firstname в качестве y5_, owner1_.lastname в качестве y6_ из отелей this_ оставил внешнее присоединение пользователей owner1_ для this_.user_id=owner1_.id

Но вы не сможете использовать criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP) потому что набор результатов не является точно изображением полей из Hostel (даже без псевдонимов). На самом деле список является List<Object[]> со строками, содержащими все отдельные поля из Hostel затем 3 обязательных поля из owner,

Вам нужно будет добавить класс-оболочку, содержащий Hostel и 3 других поля, чтобы использовать AliasToBeanResultTransformer и получить правду Hostel объекты:

public class HostelWrapper {
    private Hostel hostel;
    private int owner_id;
    private String owner_firstName;
    private String owner_lastName;

    public HostelWrapper() {
        hostel = new Hostel();
    }

    public Hostel getHostel() {
        return hostel;
    }
    public void setId(int id) {
        hostel.setId(id);
    }
    public void setOwner(User owner) {
        hostel.setOwner(owner);
    }
    // other setters for Hostel fields ...

    public int getOwner_id() {
        return owner_id;
    }
    public void setOwner_id(Integer owner_id) {
    // beware : may be null because of outer join
        this.owner_id = (owner_id == null) ? 0 : owner_id;
    }
    //getters and setters for firstName and lastName ...
}

И тогда вы можете успешно написать:

criteria.setResultTransformer(new AliasToBeanResultTransformer(HostelWrapper.class));
List<HostelWrapper> hostels = criteria.list();

Hostel hostel = hostels.get(0).getHostel();
String firstName = hostels.get(0).getFirstName();

Я мог бы проверить это, когда нет владельца hostel.getOwner() является нулевым, и когда он есть, hostel.getOwner().getId() равно getOwner_id() и что этот доступ не генерирует никаких дополнительных запросов. Но любой доступ к другому полю hostel.getOwner(), четное firstName или же lastName генерирует один, потому что User объект не был загружен в сеансе.

Наиболее распространенное использование должно быть:

for (HostelWrapper hostelw: criteria.list()) {
    Hostel hostel = hostelw.getHostel();
    // use hostel, hostelw.getOwner_firstName and hostelw.getOwner_lastName
}

@Serge Ballesta решил мою проблему, но вот мой последний рабочий код:

Criteria criteria = currenSession().createCriteria(Hostel.class);
criteria.add(Restrictions.ge("endDate", Calendar.getInstance()));
        if (StringUtils.notNullAndEmpty(country)) {
            criteria.add(Restrictions.eq("country", country));
        }
Long count = (Long) criteria
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .setProjection(Projections.rowCount()).uniqueResult();

// mark query as readonly
        criteria.setReadOnly(true);
        // descendingly sort result by rating property of Hostel entity
        criteria.addOrder(Order.desc("rating"));
        // reset rowCount() projection
        criteria.setProjection(null);

ProjectionList hostelProjList = Projections.projectionList();
        ClassMetadata hostelMetadata = getSessionFactory().getClassMetadata(
                Hostel.class);
        // add primary key property - hostelId
        hostelProjList.add(Projections.property(hostelMetadata
                .getIdentifierPropertyName()), "hostelId");
        // add all normal properties of Hostel entity to retrieve from db
        for (String prop : hostelMetadata.getPropertyNames()) {
            //skip associations
            if (!prop.equals("owner") && !prop.equals("images")
                    && !prop.equals("requests") && !prop.equals("feedbacks"))
                hostelProjList.add(Projections.property(prop), prop);
        }
        // add properties of User owner association to be retrieved
        hostelProjList
                .add(Projections.property("owner.userId"), "owner_id")
                .add(Projections.property("owner.firstName"), "owner_firstName")
                .add(Projections.property("owner.lastName"), "owner_lastName");

        // create alias to retrieve props of User owner association
        criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN);
        criteria.setProjection(hostelProjList);

        criteria.setResultTransformer(new AliasToBeanResultTransformer(
                HostelWrapper.class));

List<HostelWrapper> wrappers = criteria.list();

И мой HostelWrapper является:

public class HostelWrapper {
    private Hostel hostel;
    private int owner_id;
    private String owner_firstName;
    private String owner_lastName;

    public HostelWrapper() {
        hostel = new Hostel();
    }

    public Hostel getHostel() {
        return hostel;
    }

    public void setHostelId(Integer hostelId) {
        this.hostel.setHostelId(hostelId);
    }

public void setCountry(String country) {
        this.hostel.setCountry(country);
    }
public int getOwner_id() {
        return owner_id;
    }

    public void setOwner_id(Integer owner_id) {
        this.owner_id = owner_id == null ? 0 : owner_id;
    }

    public String getOwner_firstName() {
        return owner_firstName;
    }

    public void setOwner_firstName(String owner_firstName) {
        this.owner_firstName = owner_firstName;
    }

    public String getOwner_lastName() {
        return owner_lastName;
    }

    public void setOwner_lastName(String owner_lastName) {
        this.owner_lastName = owner_lastName;
    }

это HostelWrapper используется AliasToBeanResultTransformer сопоставить результаты гибернации с сущностями.

Мой окончательный вывод заключается в том, что HQL - правильный путь, когда вы хотите установить проекцию ассоциации. С AliasToBeanResultTransformer вы связаны с именами свойств и с hql то же самое. Преимущество в том, что HQL гораздо проще писать.

По моим наблюдениям вы не можете получить результат в соответствии с вашими требованиями.

Мы можем сделать, используя следующие шаги:

String[] propertyNames = sessionFactory.getClassMetadata(Hostel.class).getPropertyNames();

ProjectionList projectionList = Projections.projectionList();

for (String propString : propertyNames) {
                projectionList.add(Projections.property(propString));
}

Пожалуйста, измените код согласно ниже

criteria.createAlias("owner", "owner", JoinType.LEFT_OUTER_JOIN)
        .setProjection(
                projectionList
                        .add(Projections.property("owner.userId"))
                        .add(Projections.property("owner.firstName"))
                        .add(Projections.property("owner.lastName")));

Пожалуйста, попробуйте и позвольте мне знать.

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