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

无人久伴 提交于 2019-12-03 00:31:10

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 :

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

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

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

You can obtain something using Criteria, but Criteria are 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).

The only way I can imagine would be to explicitely list all fields from 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 :

    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

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 Hostel followed by the 3 required fields from owner.

You will have to add a wrapper class containing a Hostel and the 3 other fields to make use of an AliasToBeanResultTransformer and get true Hostel objects :

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 firstName or lastName generates one because the User entity was not loaded in session.

The most common usage should be :

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

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

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 HostelWrapper is :

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 HostelWrapper is used by AliasToBeanResultTransformer to map hibernate resultset to entities.

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!