Как заставить HIbernate извлекать все свойства корневого объекта и только определенные свойства связанного объекта?
у меня есть корневой объект Hostel
и ассоциации User owner
.
когда я загружаю Hostel
сущность, которую мне нужно нетерпеливо принести User owner
, но только owner
3 свойства: 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, которая выбирает только owner
С 3 свойствами, как указано в списке проекции. Но он не выбирает никакого свойства 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
s, которые пусты. Пять карт, потому что их пять Hostel
строки, соответствующие условию where. Я создал простой sql-запрос, и он отлично работает, поэтому проблема только здесь.
как заставить hibernate получить все свойства root Hostel
сущность и только 3 свойства asociated User owner
сущности?
редактировать
Я пытался использовать getSessionFactory().getClassMetadata(Hostel.class)
но это дало ошибку о сопоставлении enum в Hostel
. Поэтому я возвращаюсь к list 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 ?
At for-каждый цикл получает такую ошибку:
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$db5b21.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.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$af3bc10.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, но результирующий список "хостелы" был:
[{}, {}, {}, {}, {}]
пять пустых карт. Пять, потому что в db(table hostel) есть 5 строк, соответствующих предложению where.
затем я полностью удалил список проекций и спящий режим получил 5 хостелов и 5 связанных User owner
s и 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_.имя как col_2_0_, user1_.фамилия как col_3_0_, hostel0_.id как id1_0_, hostel0_.имя как name2_0_,..., hostel0_.owner_id как user_id4_0_ из Hostel hostel0_ оставил внешнего пользователя присоединиться user1_ на user1_.id=hostel0_.owner_id
со всеми полями из хостела и только обязательные поля от пользователей.
список, полученный с criteria.list()
это List<Object[]>
чьи строки
[ Hostel, Integer, String, String]
вы можете получить что-то используя Criteria
, а Criteria
более строгие, чем запросы. Я не смог найти API, который позволяет смешивать объекты и поля. Насколько я знаю, невозможно получить строки, содержащие объект (хостелы) и отдельные поля от ассоциации (владельца.идентификатор пользователя, владельца.имя, владелец.фамилия.)
единственный способ, который я могу себе представить, это явно перечислите все поля из хостелов:
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_.имя как y1_,..., этот._user_id как y3_, owner1_.id как y4_, owner1_.имя как y5_, owner1_.lastname as y6_ из отелей this_ left outer join users owner1_ on this_.user_id=owner1_.id
но вы не сможете использовать criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
потому что resultset-это не совсем изображение полей из Hostel
(даже без псевдонимов).
На самом деле список List<Object[]>
со строками, содержащими все отдельные поля из Hostel
затем следуют 3 обязательных поля из owner
.
вам нужно будет добавить класс-оболочку, содержащий Hostel
и 3 других поля, чтобы использовать AliasToBeanResultTransformer
и получить true 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()
равно null, и когда есть один, 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
для сопоставления набора результатов hibernate с сущностями.
мой окончательный вывод заключается в том, что 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")));
пожалуйста, попробуйте и дайте мне знать.