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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25337884/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 07:51:06  来源:igfitidea点击:

How to make HIbernate fetch all properties of root entity and only specific properties of associated entity?

javahibernatehibernate-criteria

提问by Volodymyr Levytskyi

I have root entity Hosteland its single association User owner.

我有根实体Hostel及其单一关联User owner

When I fetch Hostelentity I need to eagerly fetch User owner, but only owner's 3 properties : userId,firstName,lastName.

当我获取Hostel实体时,我需要急切地获取User owner,但只有owner3 个属性:userId、firstName、lastName。

For now my criteria query is :

现在我的标准查询是:

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

Next, I do criteria.list()and I get sql statement which selects only owner's 3 properties as specified in projection list. But it doesn't select any property of root Hostelentity. Generated query is:

接下来,我执行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 ?

This query doesn't work because it returns five Maps which are empty. FIve maps are because there are five Hostelrows that match where condition. I created simple sql query and it works fine so the problem is only here.

此查询不起作用,因为它返回五个Map空的 s。五张地图是因为有五行Hostel匹配 where 条件。我创建了简单的 sql 查询,它运行良好,所以问题仅出在这里。

How to force hibernate to fetch all properties of root Hostelentity and only 3 properties of asociated User ownerentity?

如何强制休眠获取根Hostel实体的所有属性和关联User owner实体的仅 3 个属性?

EDITI tried to use getSessionFactory().getClassMetadata(Hostel.class)but it gave error about mapping enum in Hostel. So I fallback to list Hostelproperties manually. For now my criteria query is:

编辑我尝试使用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);
        }

Note that I removed ALIAS_TO_ENTITY_MAPresult transformer. This generated such mysql query :

请注意,我删除了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-each loop get such an error:

在 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$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)

This error means that I don't have type Hostelin resulting list hostels. I even tried to find out class of elements in resulting list 'hostels' with this:

这个错误意味着我Hostel在结果列表中没有类型hostels。我什至试图在结果列表“旅馆”中找出元素类别:

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

It printed:

它打印:

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

Then I tried to set ALIAS_TO_ENTITY_MAPfor new ProjectionList, but resulting list 'hostels' was:

然后我尝试设置ALIAS_TO_ENTITY_MAP新的 ProjectionList,但结果列表 'hostels' 是:

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

Five empty maps. Five because there are 5 rows in db(table hostel) matching where clause.

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

Then I deleted projection list completely and hibernate retrieved 5 hostels and 5 associated User owners and owner's images as expected.

然后我完全删除了投影列表,并且 hibernate按预期检索了 5 个旅馆和 5 个相关的User owners 和owner's 图像。

THE PROBLEM is how to stop hibernate retrieve associated Imageentity of associated User owner. The best would be to fetch only 3 specific props of associated User owner.

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

Thank you!

谢谢!

回答by Serge Ballesta

You can do it with a direct query :

您可以通过直接查询来实现:

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

generates a SQL like :

生成一个 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

选择 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_.4host_id 为左 user_id Hostel_0外部加入用户 user1_ 在 user1_.id=hostel0_.owner_id

with all the fields from Hostel and only required fields from User.

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

The list obtained with criteria.list()is a List<Object[]>whose rows are [ Hostel, Integer, String, String]

获得的列表criteria.list()是一个,List<Object[]>其行是 [ Hostel, Integer, String, String]

You can obtain something using Criteria, but Criteriaare more strict than queries. I could not find any API that allows to mix entities and fields. So as far as I know, it is not possible to get rows containing an entity (Hostels) and separate fields from an association (owner.userId, owner.firstName, owner.lastName).

您可以使用 获得一些东西Criteria,但Criteria比查询更严格。我找不到任何允许混合实体和字段的 API。因此,据我所知,不可能从关联中获取包含实体 (Hostels) 和单独字段的行(owner.userId、owner.firstName、owner.lastName)。

The only way I can imagine would be to explicitely list all fields from Hostels :

我能想象的唯一方法是明确列出 Hostels 的所有字段:

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

You can automate it a little by using Metadata (don't forget the id ...) - note : I use aliased projection only to be able later to use a wrapper class, if you directly use the scalar values, you can safely omit the Projection.alias:

您可以使用元数据将其自动化一点(不要忘记 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();

That way correctly generates

这样正确生成

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

选择 this_.id 为 y0_,this_.name 为 y1_,...,this_.user_id 为 y3_,owner1_.id 为 y4_,owner1_.firstname 为 y5_,owner1_.lastname 为 y6_ 来自酒店 this_ 离开外部加入用户 owner1_ on this_ .user_id=owner1_.id

But you will not be able to use criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)because the resultset is not exactly the image of the fields from Hostel(even without the aliases). In fact the list is a List<Object[]>with rows containing all individual fields from Hostelfollowed by the 3 required fields from owner.

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

You will have to add a wrapper class containing a Hosteland the 3 other fields to make use of an AliasToBeanResultTransformerand get true Hostelobjects :

您必须添加一个包含 aHostel和其他 3 个字段的包装类,以使用 anAliasToBeanResultTransformer并获取真正的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 ...
}

And then you can successfully write :

然后你可以成功地写:

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

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

I could verify that when there is no owner hostel.getOwner()is null, and when there is one, hostel.getOwner().getId()is equal to getOwner_id()and that this access does not generate any extra query. But any access to an other field of hostel.getOwner(), even firstNameor lastNamegenerates one because the Userentity was not loaded in session.

我可以验证当没有所有者时hostel.getOwner()为空,当有一个时,hostel.getOwner().getId()等于getOwner_id()并且此访问不会生成任何额外的查询。但是,任何访问的其他领域hostel.getOwner(),甚至firstNamelastName产生,因为一个User实体并没有在会议上加载。

The most common usage should be :

最常见的用法应该是:

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

回答by Volodymyr Levytskyi

@Serge Ballesta solved my issue but here is my final working code:

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

And my HostelWrapperis :

而我的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;
    }

This HostelWrapperis used by AliasToBeanResultTransformerto map hibernate resultset to entities.

HostelWrapper用于AliasToBeanResultTransformer将休眠结果集映射到实体。

My final conclusion is that HQL is right way to go when you want to set projection on association. With AliasToBeanResultTransformeryou are bound to properties names and with hql is the same. Benefit is that HQL is much easier to write.

我的最终结论是,当您想在关联上设置投影时,HQL 是正确的方法。与AliasToBeanResultTransformer您绑定到属性名称和 hql 是相同的。好处是 HQL 更容易编写。

回答by Siva Kumar

As per my observation you cant get result as per you require.

根据我的观察,您无法获得所需的结果。

We can done using the following steps:

我们可以使用以下步骤完成:

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

ProjectionList projectionList = Projections.projectionList();

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

Please change the code as per below

请按照以下更改代码

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

Please try and lemme know.

请尝试让我知道。