【问题标题】:mapping Hibernate query results to custom class?将 Hibernate 查询结果映射到自定义类?
【发布时间】:2016-09-22 01:40:53
【问题描述】:

跟进我昨天发布的一个问题: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();

返回 ListResults(我的基类)。

【问题讨论】:

    标签: java spring hibernate


    【解决方案1】:
    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。它会尝试使用此类策略在DTO 中查找名称为getFIRSTNAME() 的getter 和setter setFIRSTNAME()

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

    在 Hibernate 看来,只有 PropertyAccessStrategyMapImpl.INSTANCE 适合。所以之后它会尝试进行转换(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 名称)。

    只需从项目页面下载库(不需要额外的 jar):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

    【讨论】:

    • 不错的一个!通过在别名上加上引号,我可以将我的类与 ResultTransformer 一起使用!谢谢!
    【解决方案2】:

    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 的值注入到其相应的属性或字段中。

    【讨论】:

    • 有趣的是,我可以用 Hibernate 5.1 重现这个问题。变压器Transformers.aliasToBean(CatDTO.class) 不工作。我之前只是用自己的变压器测试过 :) 尝试找出原因。
    • 这更有趣,我对别名是正确的。看我的回答:stackoverflow.com/a/37423885/3405171
    【解决方案3】:

    您需要使用构造函数并在 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;
       }
    }
    

    然后你的总部

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 这很有帮助。谢谢!
      • 我爱你,如果可以的话,我会给这个答案100分。
      【解决方案5】:

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

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

      可能是因为:

      • 列名大小写不匹配或

      • 列名不匹配或

      • 查询中存在列,但类中缺少列。

      【讨论】:

        【解决方案6】:

        如果您有本机查询,这里的所有答案都使用新版本的 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()
        

        【讨论】:

          【解决方案7】:

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

          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】:

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

          1. 自定义查询
          2. 带有可选参数的自定义查询
          3. 将 Hibernate 自定义查询结果映射到自定义类。

          我并不是说自定义 EntityRepository 接口扩展了 SpringBoot 上的 JpaRepository,您可以使用 @Query 编写自定义查询 -> 在这里您不能使用可选参数编​​写查询 例如如果 param 为 null,则不要将其附加到查询字符串中。您可以使用休眠的 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());
          
              }
          

          【讨论】:

            【解决方案9】:

            来自 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

            【讨论】:

              猜你喜欢
              • 2014-02-11
              • 2020-08-17
              • 2020-04-02
              • 1970-01-01
              • 2017-10-11
              • 2023-03-26
              • 2012-04-28
              • 2018-08-16
              • 1970-01-01
              相关资源
              最近更新 更多