将Hibernate查询结果映射到自定义类?

时间:2016-05-24 17:41:07

标签: java spring hibernate

跟进我昨天发布的问题:How to populate POJO class from custom Hibernate query?

有人能告诉我一个如何在Hibernate中编写以下SQL的示例,并正确获取结果吗?

SQL:

select firstName, lastName
from Employee

如果在Hibernate中可行的话,我想做的就是将结果放在他们自己的基类中:

class Results {
    private firstName;
    private lastName;
    // getters and setters
}

我相信它在JPA中可行(使用EntityManager),但我还没有想出如何在Hibernate中使用SessionFactorySession )。

我试图更好地学习Hibernate,甚至这个"简单"查询被证明是混乱的,知道Hibernate返回结果的形式,以及如何将结果映射到我自己的(基础)类。所以在DAO例程结束时,我会这样做:

List<Results> list = query.list();

返回List Results(我的基类)。

9 个答案:

答案 0 :(得分:14)

select firstName, lastName from Employee

query.setResultTransformer(Transformers.aliasToBean(MyResults.class));

你不能在Hibernate 5和Hibernate 4(至少是Hibernate 4.3.6.Final)上使用上面的代码,因为异常

java.lang.ClassCastException: com.github.fluent.hibernate.request.persistent.UserDto cannot be cast to java.util.Map
    at org.hibernate.property.access.internal.PropertyAccessMapImpl$SetterImpl.set(PropertyAccessMapImpl.java:102)

问题是Hibernate将列名称的别名转换为大写 - firstName变为FIRSTNAME。并尝试使用此类策略在getFIRSTNAME()中找到名为setFIRSTNAME()的getter和setter DTO

    PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
            PropertyAccessStrategyBasicImpl.INSTANCE,
            PropertyAccessStrategyFieldImpl.INSTANCE,
            PropertyAccessStrategyMapImpl.INSTANCE
    );

只有PropertyAccessStrategyMapImpl.INSTANCE适合Hibernate,很好。因此,之后它会尝试进行转换(Map)MyResults

public void set(Object target, Object value, SessionFactoryImplementor factory) {
    ( (Map) target ).put( propertyName, value );
}

不知道,这是一个错误或功能。

如何解决

使用带引号的别名

public class Results {

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

String sql = "select firstName as \"firstName\", 
    lastName as \"lastName\" from Employee";

List<Results> employees = session.createSQLQuery(sql).setResultTransformer(
    Transformers.aliasToBean(Results.class)).list(); 

使用自定义结果转换器

解决问题的另一种方法 - 使用忽略方法名称的结果转换器(将getFirstName()视为getFIRSTNAME())。您可以自己编写或使用FluentHibernateResultTransformer。您不需要使用引号和别名(如果列名等于DTO名称)。

只需从项目页面下载该库(它不需要额外的罐子):fluent-hibernate

String sql = "select firstName, lastName from Employee";
List<Results> employees = session.createSQLQuery(sql)
        .setResultTransformer(new FluentHibernateResultTransformer(Results.class))
        .list();

此变换器也可用于嵌套投影:How to transform a flat result set using Hibernate

答案 1 :(得分:10)

请参阅AliasToBeanResultTransformer

  

结果转换器,允许将结果转换为用户指定的类,该类将通过setter方法或与别名相匹配的字段填充。

List resultWithAliasedBean = s.createCriteria(Enrolment.class)
            .createAlias("student", "st")
            .createAlias("course", "co")
            .setProjection( Projections.projectionList()
                    .add( Projections.property("co.description"), "courseDescription" )
            )
            .setResultTransformer( new AliasToBeanResultTransformer(StudentDTO.class) )
            .list();

StudentDTO dto = (StudentDTO)resultWithAliasedBean.get(0);

您修改过的代码:

List resultWithAliasedBean = s.createCriteria(Employee.class, "e")
    .setProjection(Projections.projectionList()
        .add(Projections.property("e.firstName"), "firstName")
        .add(Projections.property("e.lastName"), "lastName")
    )
    .setResultTransformer(new AliasToBeanResultTransformer(Results.class))
    .list();

Results dto = (Results) resultWithAliasedBean.get(0);

对于本机SQL查询,请参阅Hibernate documentation

  

<强> 13.1.5。返回非托管实体

     

可以将ResultTransformer应用于本机SQL查询,允许它返回非托管实体。

sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
    .setResultTransformer(Transformers.aliasToBean(CatDTO.class))
     

此查询指定:

     
      
  • SQL查询字符串
  •   
  • 结果变压器   上面的查询将返回已经实例化的CatDTO列表,并将NAME和BIRTHNAME的值注入其相应的属性或字段中。
  •   

答案 2 :(得分:5)

你需要使用构造函数并在hql中使用new。我告诉你从这个问题中获取的代码示例:hibernate HQL createQuery() list() type cast to model directly

class Result {
    private firstName;
    private lastName;
    public Result (String firstName, String lastName){
      this.firstName = firstName;
      this.lastName = lastName;
   }
}

然后你的hql

select new com.yourpackage.Result(employee.firstName,employee.lastName) 
from Employee  

和你的java(使用Hibernate)

List<Result> results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();

答案 3 :(得分:1)

YMMV但我发现关键因素是您必须确保使用SQL“AS”关键字为SELECT子句中的每个字段设置别名。我从来没有在别名周围使用引号。此外,在SELECT子句中,使用数据库和别名中实际列的大小写和标点符号,使用POJO中字段的大小写。这在Hibernate 4和5中对我有用。

@Autowired
private SessionFactory sessionFactory;

...

String sqlQuery = "SELECT firstName AS firstName," +
        "lastName AS lastName from Employee";

List<Results> employeeList = sessionFactory
        .getCurrentSession()
        .createSQLQuery(sqlQuery)
        .setResultTransformer(Transformers.aliasToBean(Results.class))
        .list();

如果您有多个表,也可以在SQL中使用表别名。这个设计的示例带有一个名为“Department”的附加表,在POJO字段名称中使用驼峰大小写的数据库字段名称中使用更传统的小写和下划线。

String sqlQuery = "SELECT e.first_name AS firstName, " +
        "e.last_name AS lastName, d.name as departmentName" +
        "from Employee e, Department d" +
        "WHERE e.department_id - d.id";

List<Results> employeeList = sessionFactory
        .getCurrentSession()
        .createSQLQuery(sqlQuery)
        .setResultTransformer(Transformers.aliasToBean(Results.class))
        .list();

答案 4 :(得分:1)

java.lang.ClassCastException: "CustomClass" cannot be cast to java.util.Map.

当SQL Query中指定的列与映射类的列不匹配时,会出现此问题。

可能是由于:

  • 列名称不匹配的大小写或

  • 列名称不匹配或

  • 列存在于查询中但在类中缺失。

答案 5 :(得分:0)

如果你有一个原生查询,这里的所有答案都使用新版Hibernate的弃用方法,所以如果你使用的是5.1+,这就是要走的路:

// Note this is a org.hibernate.query.NativeQuery NOT Query.
NativeQuery query = getCurrentSession()
                .createNativeQuery(
                        "SELECT {y.*} , {x.*} from TableY y left join TableX x on x.id = y.id");


// This maps the results to entities. 
query.addEntity("x", TableXEntity.class);
query.addEntity("y", TableYEntity.class);

query.list()

答案 6 :(得分:0)

写作(存在这种与hibernate一起使用的挑战)

  1. 自定义查询
  2. 具有可选参数的自定义查询
  3. 将Hibernate自定义查询结果映射到Custom类。
  4. 我不是说自定义EntityRepository接口在SpringBoot上扩展了JpaRepository,您可以使用@Query编写自定义查询 - &gt;在这里你不能用可选参数写查询 例如如果param为null,则不将其追加到查询字符串中。你可以使用hibernate的Criteria api,但由于性能问题,不建议在他们的文档中使用...

    但存在简单易错且性能良好的方式......

    编写自己的QueryService类,这些方法将获得  字符串(回答第一个和第二个问题)sql并将结果映射到  自定义类(第三个问题)与它的任何关联@OneToMany,@ ManyToOne ....

    @Service
    @Transactional
    public class HibernateQueryService {
    
        private final Logger log = LoggerFactory.getLogger(HibernateQueryService.class);
        private JpaContext jpaContext;
    
        public HibernateQueryService(JpaContext jpaContext) {
            this.jpaContext = jpaContext;
        }
    
        public List executeJPANativeQuery(String sql, Class entity){
            log.debug("JPANativeQuery executing: "+sql);
            EntityManager entityManager = jpaContext.getEntityManagerByManagedType(Article.class);
            return entityManager.createNativeQuery(sql, entity).getResultList();
        }
    
    /**
     * as annotation @Query -> we can construct here hibernate dialect 
     * supported query and fetch any type of data
     * with any association @OneToMany and @ManyToOne.....
     */
        public List executeHibernateQuery(String sql, Class entity){
            log.debug("HibernateNativeQuery executing: "+sql);
            Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
            return session.createQuery(sql, entity).getResultList();
        }
    
    public <T> List<T> executeGenericHibernateQuery(String sql, Class<T> entity){
        log.debug("HibernateNativeQuery executing: "+sql);
        Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
        return session.createQuery(sql, entity).getResultList();
    }
    
    
    }
    

    用例 - 您可以编写有关查询参数的任何类型条件

     @Transactional(readOnly = true)
        public List<ArticleDTO> findWithHibernateWay(SearchFiltersVM filter){
    
            Long[] stores = filter.getStores();
            Long[] categories = filter.getCategories();
            Long[] brands = filter.getBrands();
            Long[] articles = filter.getArticles();
            Long[] colors = filter.getColors();
    
            String query = "select article from Article article " +
                "left join fetch article.attributeOptions " +
                "left join fetch article.brand " +
                "left join fetch article.stocks stock " +
                "left join fetch stock.color " +
                "left join fetch stock.images ";
    
    boolean isFirst = true;
    
            if(!isArrayEmptyOrNull(stores)){
                query += isFirst ? "where " : "and ";
                query += "stock.store.id in ("+ Arrays.stream(stores).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(brands)){
                query += isFirst ? "where " : "and ";
                query += "article.brand.id in ("+ Arrays.stream(brands).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(articles)){
                query += isFirst ? "where " : "and ";
                query += "article.id in ("+ Arrays.stream(articles).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(colors)){
                query += isFirst ? "where " : "and ";
                query += "stock.color.id in ("+ Arrays.stream(colors).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
            }
    
            List<Article> articles = hibernateQueryService.executeHibernateQuery(query, Article.class);
    
    
          /**
            *  MapStruct [http://mapstruct.org/][1]
            */
            return articles.stream().map(articleMapper::toDto).collect(Collectors.toList());
    
        }
    

答案 7 :(得分:0)

下面是忽略大小写的结果转换器:

package org.apec.abtc.dao.hibernate.transform;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;

/**
 * IgnoreCaseAlias to BeanResult Transformer
 * 
 * @author Stephen Gray
 */
public class IgnoreCaseAliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer
{

    /** The serialVersionUID field. */
    private static final long serialVersionUID = -3779317531110592988L;

    /** The resultClass field. */
    @SuppressWarnings("rawtypes")
    private final Class resultClass;
    /** The setters field. */
    private Setter[] setters;
    /** The fields field. */
    private Field[] fields;
    private String[] aliases;

    /**
     * @param resultClass
     */
    @SuppressWarnings("rawtypes")
    public IgnoreCaseAliasToBeanResultTransformer(final Class resultClass)
    {
        if (resultClass == null)
        {
            throw new IllegalArgumentException("resultClass cannot be null");
        }
        this.resultClass = resultClass;
        this.fields = this.resultClass.getDeclaredFields();
    }

    @Override
    public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object transformTuple(final Object[] tuple, final String[] aliases)
    {
        Object result;

        try
        {
            if (this.setters == null)
            {
                this.aliases = aliases;

                setSetters(aliases);
            }
            result = this.resultClass.newInstance();

            for (int i = 0; i < aliases.length; i++)
            {
                if (this.setters[i] != null)
                {
                    this.setters[i].set(result, tuple[i], null);
                }
            }
        }
        catch (final InstantiationException | IllegalAccessException e)
        {
            throw new HibernateException("Could not instantiate resultclass: " + this.resultClass.getName(), e);
        }

        return result;
    }

    private void setSetters(final String[] aliases)
    {
        PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
                                                                                                         PropertyAccessStrategyBasicImpl.INSTANCE,
                                                                                                         PropertyAccessStrategyFieldImpl.INSTANCE,
                                                                                                         PropertyAccessStrategyMapImpl.INSTANCE
                                                                        );

        this.setters = new Setter[aliases.length];
        for (int i = 0; i < aliases.length; i++)
        {
            String alias = aliases[i];
            if (alias != null)
            {
                for (final Field field : this.fields)
                {
                    final String fieldName = field.getName();
                    if (fieldName.equalsIgnoreCase(alias))
                    {
                        alias = fieldName;
                        break;
                    }
                }
                setters[i] = propertyAccessStrategy.buildPropertyAccess( resultClass, alias ).getSetter();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("rawtypes")
    public List transformList(final List collection)
    {
        return collection;
    }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }
        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }

        IgnoreCaseAliasToBeanResultTransformer that = ( IgnoreCaseAliasToBeanResultTransformer ) o;

        if ( ! resultClass.equals( that.resultClass ) ) {
            return false;
        }
        if ( ! Arrays.equals( aliases, that.aliases ) ) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = resultClass.hashCode();
        result = 31 * result + ( aliases != null ? Arrays.hashCode( aliases ) : 0 );
        return result;
    }
}

答案 8 :(得分:0)

来自 hibernate 5.4 的 JPQL 案例:

Query<Employee> queryList = session.createQuery("select new xxx.xxx.Employee(e.firstName,e.lastName) from Employee e", Employee.class);
List<Employee> list = queryList.list();

Query<Long> queryCount = session.createQuery("select count(*) from Employee", Long.class);
Long count = queryCount.getSingleResult();
  • JPQL 中的 select 语句与 HQL 完全相同,只是 JPQL 需要 select_clause,而 HQL 不需要。
  • setResultTransformer @Deprecated 不应使用
  • 更多关于Hibernate_User_Guide.html#hql-select