java - 如何让 HIbernate 获取根实体的所有属性以及关联实体的特定属性?

标签 java hibernate hibernate-criteria

我有根实体 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 个属性。但它不会选择根 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。五张 map 是因为有五个 Hostel 行符合 where 条件。我创建了简单的 sql 查询,它工作正常,所以问题只在这里。

如何强制 hibernate 获取根 Hostel 实体的所有属性以及关联的 User owner 实体的仅 3 个属性?

编辑 我尝试使用 getSessionFactory().getClassMetadata(Hostel.class) 但它给出了有关 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)

此错误意味着我在结果列表 hostels 中没有类型 Hostel。 我什至试图通过以下方式找出结果列表“旅馆”中的元素类:

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

它打印了:

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

然后我尝试为新的 ProjectionList 设置 ALIAS_TO_ENTITY_MAP,但结果列表“旅馆”是:

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

五张空 map 。五是因为 db(table hostel) 中有 5 行匹配 where 子句。

然后我完全删除了投影列表, hibernate 检索了 5 个旅馆和 5 个关联的 User ownerowner 的图像,如预期的那样。

问题是如何停止 hibernate 检索关联 User owner 的关联 Image 实体。最好的办法是只获取关联 User owner 的 3 个特定 Prop 。

谢谢!

最佳答案

您可以通过直接查询来完成:

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:

select hostel0_.id as col_0_0_, user1_.id as col_1_0_, user1_.firstname as col_2_0_, user1_.lastname as col_3_0_, hostel0_.id as id1_0_, hostel0_.name as name2_0_, ..., hostel0_.owner_id as user_id4_0_ from Hostel hostel0_ left outer join User user1_ on user1_.id=hostel0_.owner_id

包含 Hostel 的所有字段和 User 的必填字段。

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

你可以通过使用元数据(不要忘记 id ...)稍微自动化它 - 注意:我使用别名投影只是为了以后能够使用包装类,如果你直接使用标量值,你可以安全地省略 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();

这种方式正确生成

select this_.id as y0_, this_.name as y1_, ..., this_.user_id as y3_, owner1_.id as y4_, owner1_.firstname as y5_, owner1_.lastname as y6_ from hotels this_ left outer join users owner1_ on this_.user_id=owner1_.id

但是您将无法使用criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)因为结果集不完全是 Hostel 中字段的图像(即使没有别名)。 事实上,列表是一个 List<Object[]>行包含来自 Hostel 的所有单独字段后跟来自 owner 的 3 个必填字段.

您必须添加一个包含 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() , 甚至 firstNamelastName生成一个是因为 User实体未加载到 session 中。

最常见的用法应该是:

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

关于java - 如何让 HIbernate 获取根实体的所有属性以及关联实体的特定属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25337884/

相关文章:

java - java中如何在没有空检查的情况下避免NullpointerException

java - 如何检查 url 中是否存在协议(protocol)?

java - 有些 java 方法是 null 安全的,有些不是,我怎么知道?

java - 在 hibernate 中使用多个表,输出问题

java - JPA/Hibernate persist 似乎不起作用

hibernate - 如何在可以为空的字段上创建条件?

java - hibernate criteria group查询错误

java - 如何下载 Maven jar

java - blob sql 类型的 Hibernate 验证问题

带内连接的 Hibernate 更新查询