【问题标题】:Getting column names from a JPA Native Query从 JPA 原生查询中获取列名
【发布时间】:2017-11-21 10:28:34
【问题描述】:

我的 Web 应用程序中有一个管理控制台,允许管理员对我们的数据库执行自定义 SQL SELECT 查询。

在下面,应用程序正在使用 Hibernate,但这些查询不是 HQL,它们是纯 SQL,所以我使用的是这样的 Native Query:

protected EntityManager em;

public List<Object[]> execute(String query) {
    Query q = em.createNativeQuery(query);
    List<Object[]> result = q.getResultList();
    return result;
}

这可以正常工作,但它只返回数据行,没有额外信息。我还想要获取列名,因此当我将结果打印回用户时,我还可以打印一个标题以显示各个列是什么。

有什么办法吗?

【问题讨论】:

  • 您的查询是什么样的?
  • @YCF_L:如果我事先知道,我不会有这个问题,因为我会知道列名;)正如我在问题中所写,这是一个管理控制台,管理员可以在其中键入任何 SQL SELECT 查询并将结果打印在表上。所以任何 SQL 查询都是可能的,只要它是一个 SELECT。 SELECT name, surname FROM users 只是举一个愚蠢的例子,但它可以是任何东西。
  • 我不确定 Hibernate 是否支持检索查询元数据。纯 JDBC 是您可以使用的选项吗?如果是这样,您可以从包含列标题的 ResultSet 中检索 ResultSetMetaData 对象。
  • 将结果类设置为Tuple,然后你应该得到列/字段的名称和值。标准 JPA API,所以没有真正的理由为什么原生查询不能使用它datanucleus.org/products/accessplatform_5_1/jpa/…
  • @BillyFrost:感谢您的建议,但我认为这仅适用于映射实体。正如我所说,我使用的是本机 sql 查询。如果我尝试你的方法,我会得到一个org.hibernate.MappingException: Unknown entity: javax.persistence.Tuple

标签: java sql hibernate jpa


【解决方案1】:

如果 JPA 提供程序不支持检索查询元数据,另一种解决方案可能是使用 SQL 解析器,如 JSQLParserZQLGeneral SQL Parser(商业),它从 @987654324 中提取字段@ 声明。

【讨论】:

    【解决方案2】:

    很久没有任何答案,而且根据我自己的进一步研究,似乎不可能,不幸的是。

    【讨论】:

    • 这些库是否适合您:javax.persistence.Queryorg.eclipse.persistence.config.QueryHintsorg.eclipse.persistence.config.ResultType?您可以将查询设置为QueryHints.RESULT_TYPE, ResultType.Map:myEntityMananger.createNativeQuery("select * from mytable").setHint(QueryHints.RESULT_TYPE, ResultType.Map);。然后通过映射键访问列名
    • @woodz 嘿,woodz,我正在尝试使用 setHint() 正如您在评论中所说的那样,我首先创建一个查询,然后设置提示。它返回什么样的结果?你能举个例子吗?
    • 美好的一天@vc73:我不确定我还能展示什么。你是否已经用你的具体查询条件填写了我上面给定的代码模板?那么,你有没有看过你的结果列表?我正在向您展示再次处理的步骤://your resulting listList myResult = res;//your query via the entity managerres = myEntityMananger.createNativeQuery("select * from mytable") .setHint(QueryHints.RESULT_TYPE, ResultType.Map) .getResultList(); 看看您的res 变量,您可以转换列表的每个条目:int i = 1;List&lt;Map&gt; data = (List)res.get(i);
    • 原来返回的对象是一个List&lt;ArrayRecord&gt;(github.com/eclipse-ee4j/eclipselink/blob/master/foundation/…)
    • @WileE.Genius:很高兴在这里为您提供帮助。这个平台不是为了帮助人们,而是为了遵守一个法律和秩序的系统。由于我不确定eclipse链接如何或是否适合休眠,所以这里的人们肯定会抱怨“eclipse链接”不是原始问题的标签,因此会告诉我“这不能回答问题”加上放贬值。
    【解决方案3】:

    我在使用 JPA 时也遇到了类似的问题。 JPA 中没有直接访问结果集元数据的方法。解决方案可以是从查询本身中提取列名,也可以使用 JDBC 来获取元数据。

    【讨论】:

      【解决方案4】:

      将查询转换为休眠查询,然后使用休眠方法

                //normal use, javax.persistence.Query interface
          Query dbQuery = entityManager.createNativeQuery(sql);
          //cast to hibernate query
          org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)dbQuery)
                  .getHibernateQuery();
          hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
      
          List<Map<String,Object>> res = hibernateQuery.list();
      
          List<TxTestModel> txTestModels = new ArrayList<>();
          res.forEach(e->{
              TxTestModel txTestModel = new ObjectMapper().convertValue(e, TxTestModel.class);
          //  txTestModels.add(new TxTestModel().setIdd((Integer) e.get("idd")).setMmm((String) e.get("mmm")).setDdd((Date) e.get("ddd")));
              txTestModels.add(txTestModel);
          });
          System.out.println(txTestModels.size());
      

      【讨论】:

      • ((org.hibernate.jpa.HibernateQuery).getHibernateQuery(); 出错。 org.hibernate.query.internal.NativeQueryImpl 无法转换为 org.hibernate.jpa.HibernateQuery。
      • @AliSohrabi 我在 springboot1.5.1.RELEASE 中使用了这段代码,它依赖于 hibernate-core:5.0.11.Final
      【解决方案5】:

      这段代码对我有用

      DTO 类:

       public class ItemResponse<T> {
      
       private T item;
      
       public ItemResponse() {
       }
      
       public ItemResponse(T item) {
         super();
         this.item = item;
       }
      
       public T getItem() {
          return item;
      }
      
      public void setItem(T item) {
          this.item = item;
      }
      
      }
      

      服务类在下面

      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      import javax.persistence.Query;
      import org.springframework.stereotype.Service;
      import org.hibernate.transform.AliasToEntityMapResultTransformer;
      
      @Service
      public class ServiceClass{ 
      
      @PersistenceContext
      public EntityManager entityManager;
      
      public ItemResponse exceuteQueryResponse(String queryString) {
      
              ItemResponse itemResponse=new ItemResponse();           
              Query jpaQuery =  entityManager.createNativeQuery(queryString);
              org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery();
            hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
              List<Map<String,Object>> res = hibernateQuery.list();
      
              itemResponse.setItem(res);
              return itemResponse;
      
          }
      
          }
      

      【讨论】:

        【解决方案6】:

        2020

        使用 hibernate 5.2.11.Final 实际上很容易。 在我的示例中,您可以看到我如何获取每一行的列名。以及如何按列名获取值。

        Query q = em.createNativeQuery("SELECT columnA, columnB FROM table");
        List<Tuple> result = q.getResultList();
        
        for (Tuple row: result){
        
            // Get Column Names
            List<TupleElement<Object>> elements = row.getElements();
            for (TupleElement<Object> element : elements ) {
                System.out.println(element.getAlias());
            }
        
            // Get Objects by Column Name
            Object columnA;
            Object columnB;
            try {
                columnA = row.get("columnA");
                columnB= row.get("columnB");
            } catch (IllegalArgumentException e) {
                System.out.println("A column was not found");
            }
        }
        

        【讨论】:

        • 此代码有效,但特别需要 Hibernate 5.2.11.Final。我实际上让它在生产中运行。 我怀疑否定投票的人可能是在不同的 Hibernate 版本中进行了测试。
        • 代码没有回答问题。使用您的解决方案,您需要事先了解这些列。
        • 感谢您的留言,@Nicolas。我刚刚编辑了我的示例来执行这两项操作,打印列名,并按列名获取值。
        • 似乎这不适用于 5.4.2.Final 等更高版本,因为返回的是对象列表。
        【解决方案7】:

        Ryiad 的回答 DTO 增加了一些混乱,你应该远离它。 你应该解释说它只适用于休眠。

        如果你像我一样需要保持列的顺序,你可以指定你自己的转换器。我从hibernate复制代码并将HashMap更改为LinkedHashMap:

        import java.util.LinkedHashMap;
        import java.util.Map;
        
        import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
        import org.hibernate.transform.ResultTransformer;
        
        /**
         * {@link ResultTransformer} implementation which builds a map for each "row", made up of each aliased value where the
         * alias is the map key. Inspired by {@link org.hibernate.transform.AliasToEntityMapResultTransformer}, but kepping the
         * ordering of elements.
         * <p/>
         * Since this transformer is stateless, all instances would be considered equal. So for optimization purposes we limit
         * it to a single, singleton {@link #INSTANCE instance}.
         */
        public class AliasToEntityMapResultTransformer extends AliasedTupleSubsetResultTransformer {
        
            public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer();
        
            /**
             * Disallow instantiation of AliasToEntityMapResultTransformer.
             */
            private AliasToEntityMapResultTransformer() {
            }
        
            @Override
            public Object transformTuple(Object[] tuple, String[] aliases) {
                Map result = new LinkedHashMap<>(tuple.length);
                for (int i = 0; i < tuple.length; i++) {
                    String alias = aliases[i];
                    if (alias != null) {
                        result.put(alias, tuple[i]);
                    }
                }
                return result;
            }
        
            @Override
            public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
                return false;
            }
        
            /**
             * Serialization hook for ensuring singleton uniqueing.
             *
             * @return The singleton instance : {@link #INSTANCE}
             */
            private Object readResolve() {
                return INSTANCE;
            }
        }
        

        有了这个转换器,你可以在 Hibernate 中使用 Ryiad 的解决方案:

            Query jpaQuery =  entityManager.createNativeQuery(queryString);
            org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery();
          hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
            List<Map<String,Object>> res = hibernateQuery.list();
        

        【讨论】:

          【解决方案8】:

          要强制 em.createNativeQuery(..).getResultList() 返回 List&lt;Tuple&gt; 在创建本机查询时使用 Tuple.class 指定它:

          Query q = em.createNativeQuery("SELECT columnA, columnB FROM table", Tuple.class );
          
          List<Tuple> result = q.getResultList();
          
          for (Tuple row: result){
          
              // Get Column Names
              List<TupleElement<Object>> elements = row.getElements();
              for (TupleElement<Object> element : elements ) {
                  System.out.println(element.getAlias());
              }
          
              // Get Objects by Column Name
              Object columnA;
              Object columnB;
              try {
                  columnA = row.get("columnA");
                  columnB= row.get("columnB");
              } catch (IllegalArgumentException e) {
                  System.out.println("A column was not found");
              }
          
          }
          

          【讨论】:

            【解决方案9】:
            Query query = entityManager.createNamedQuery(namedQuery);
            NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
            nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
            List<Map<String,Object>> result = nativeQuery.getResultList();
            

            现在你有了 Map 。你可以看到你的列名

            【讨论】:

            • 非常感谢你花了一整天的时间找到了解决方案:) 你拯救了我的一天
            【解决方案10】:

            这对我有用:

                final Query emQuery = em.createNativeQuery(query, Tuple.class);
                final List<Tuple> queryRows = emQuery.getResultList();
            
                final List<Map<String, Object>> formattedRows = new ArrayList<>();
            
                queryRows.forEach(row -> {
                    final Map<String, Object> formattedRow = new HashMap<>();
            
                    row.getElements().forEach(column -> {
                        final String columnName = column.getAlias();
                        final Object columnValue = row.get(column);
            
                        formattedRow.put(columnName, columnValue);
                    });
                    
                    formattedRows.add(formattedRow);
                });
            
                return formattedRows;
            

            【讨论】:

              猜你喜欢
              • 2018-08-11
              • 2011-08-31
              • 1970-01-01
              • 1970-01-01
              • 2013-08-20
              • 1970-01-01
              • 2012-04-03
              • 1970-01-01
              • 2011-10-16
              相关资源
              最近更新 更多