【问题标题】:Can I get the SQL string from a JPA Query object?我可以从 JPA 查询对象中获取 SQL 字符串吗?
【发布时间】:2011-09-10 16:37:10
【问题描述】:

我可以知道如何从 JPA 查询中获取 sql 吗?或者说,将 JPA 查询转换为 SQL 字符串?非常感谢!

【问题讨论】:

  • 你能更明确地说明你想要什么吗?即请发布“JPA 查询”(条件查询?JPQL?),也许是您期望的输出类型的示例。还请告诉我们您使用的是哪种 JPA 实现(OpenJPA、Hibernate 等)
  • 对于休眠实现:query.unwrap(org.hibernate.Query.class).getQueryString()。

标签: sql hibernate jpa logging jpql


【解决方案1】:

对于 Eclipselink:您可以通过以下方式提取 SQL:

query.unwrap(EJBQueryImpl.class).getDatabaseQuery().getSQLString()

它仅在执行查询之后起作用。

【讨论】:

  • 为我工作,但我的项目没有 EJBQueryImpl.class(我使用 String rtn = query.unwrap(org.hibernate.Query.class).getQueryString();)
  • @John 如果您使用过“org.hibernate.Query.class”,那么您应该只获得 JPQL,而不是原始 SQL。我想从 JPQL(使用休眠供应商)获取原始查询,有什么选择吗?
【解决方案2】:

如果您只想知道您的 JPQL 或 Criteria 查询是如何转换为数据库的 SQL 方言的,您可以在 persistence xml 中启用细粒度日志记录,然后查看您的日志文件。

属性名称和值取决于您的 JPA 实现。以下是 EclipseLink 的 persistence.xml 相关部分的示例:

<properties>
    <property name="eclipselink.logging.level" value="FINEST"/>
</properties>

【讨论】:

    【解决方案3】:

    JPA 规范

    虽然没有标准的 JPA 功能来实现这一目标,但您仍然可以使用 JPA 提供程序特定的 API 从 JPQL 或 Criteria API Query 提取 SQL 查询。

    休眠类型

    从 2.9.11 版本开始,Hibernate Types 开源项目提供了SQLExtractor 实用程序,允许您从任何 JPQL 或 Criteria API 查询中获取 SQL 查询,无论您使用的是 Hibernate 5.4、5.3 、5.2、5.1、5.0、4.3、4.2 或 4.1。

    从 JPQL 查询中获取 SQL 语句

    假设我们有以下 JPQL 查询:

    Query jpql = entityManager.createQuery("""
        select 
           YEAR(p.createdOn) as year, 
           count(p) as postCount 
        from 
           Post p 
        group by 
           YEAR(p.createdOn)
        """, Tuple.class
    );
    

    使用 Hibernate 类型,提取 Hibernate 生成的 SQL 查询就这么简单:

    String sql = SQLExtractor.from(jpql);
    

    而且,如果我们记录提取的 SQL 查询:

    LOGGER.info("""
        The JPQL query: [
            {}
        ]
        generates the following SQL query: [ 
            {}
        ]
        """,
        jpql.unwrap(org.hibernate.query.Query.class).getQueryString(),
        sql
    );
    

    我们得到以下输出:

    - The JPQL query: [
        select    
            YEAR(p.createdOn) as year,    
            count(p) as postCount 
        from    
            Post p 
        group by    
            YEAR(p.createdOn)
    ]
    generates the following SQL query: [
        SELECT 
            extract(YEAR FROM sqlextract0_.created_on) AS col_0_0_,
            count(sqlextract0_.id) AS col_1_0_
        FROM 
            post p
        GROUP BY 
            extract(YEAR FROM p.created_on)
    ]
    

    请注意,我们将 JPA Query 解包到 Hibernate org.hibernate.query.Query 接口,该接口提供了 getQueryString 方法,我们可以使用它来记录关联的 JPQL 查询字符串。

    从 JPA Criteria API 查询中获取 SQL 语句

    SQLExtractor 不限于 JPQL 查询。您也可以将它与 Criteria API 查询一起使用,如下例所示:

    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    
    CriteriaQuery<PostComment> criteria = builder.createQuery(PostComment.class);
    
    Root<PostComment> postComment = criteria.from(PostComment.class);
    Join<PostComment, Post> post = postComment.join("post");
    
    criteria.where(
        builder.like(post.get("title"), "%Java%")
    );
    
    criteria.orderBy(
        builder.asc(postComment.get("id"))
    );
    
    Query criteriaQuery = entityManager.createQuery(criteria);
    
    String sql = SQLExtractor.from(criteriaQuery);
    
    assertNotNull(sql);
    
    LOGGER.info("""
        The Criteria API, compiled to this JPQL query: [
            {}
        ]
        generates the following SQL query: [
            {}
        ]
        """,
        jpql.unwrap(org.hibernate.query.Query.class).getQueryString(),
        sql
    );
    

    在运行上述测试用例时,我们得到如下 SQL 查询:

    - The Criteria API, compiled to this JPQL query: [
        select 
            pc 
        from 
            PostComment as pc 
        inner join 
            pc.post as p 
        where 
            p.title like :param0 
        order by 
            pc.id asc
    ]
    generates the following SQL query: [
        SELECT 
            pc.id AS id1_1_,
            pc.post_id AS post_id3_1_,
            pc.review AS review2_1_
        FROM 
            post_comment pc
        INNER JOIN 
            post p ON pc.post_id=p.id
        WHERE 
            p.title LIKE ?
        ORDER BY 
            pc.id ASC
    ]
    

    Criteria API 首先编译为 JPQL 查询,如 getQueryString() 方法调用所示。

    中间 JPQL 查询进一步转换为 SQL 查询,由SQLExtractor 实用程序正确解析。

    【讨论】:

    • 是否可以使用这种方法获取带有值而不是 (?) 的 SQL?
    • 感谢您的链接。它会在日志中打印值,但它会在使用您的方法生成 SQL 时打印值吗?
    • SQLExtractor 无法提取绑定参数。这些只能在执行Query 之前提取。那时您只能确定用户已完成参数设置。否则,可以在调用 setParameter 之前提取 SQL。
    • @VladMihalcea 我正在考虑使用 SQLExtractor 创建一个部分查询,然后我可以修改添加一些本机 SQL。这会造成一些性能问题吗?
    • 最好使用专用的查询构建,例如 jooq。它也是类型安全的。
    【解决方案4】:

    按照 Karol 的回答 - 可以在 EclipseLink 中执行语句之前检索 SQL:

    Session session = em.unwrap(JpaEntityManager.class).getActiveSession();
    DatabaseQuery databaseQuery = query.unwrap(EJBQueryImpl.class).getDatabaseQuery();
    databaseQuery.prepareCall(session, new DatabaseRecord());
    Record r = databaseQuery.getTranslationRow();
    String bound = databaseQuery.getTranslatedSQLString(session, r);
    String sqlString = databaseQuery.getSQLString();
    

    要在执行期间/之后检索 SQL 字符串,最好使用持久性属性而不是代码内:

    <property name="eclipselink.logging.parameters" value="true"/>
    <property name="eclipselink.logging.level" value="FINE"/>
    

    【讨论】:

      【解决方案5】:

      除了像@Matt Handy 提到的那样启用日志记录之外,还可以在运行时使用 eclipselink 获取特定查询的 SQL 字符串,如 here 所述。

      【讨论】:

        【解决方案6】:

        如果有一种方法可以从 javax.persistence.Query(其中一个可能的子类更多精确),- 在这种情况下,根据 JPA 规范合同是不可能的。但是,这可能通过 JPA 实现来实现(例如,NamedQueryImpl 可能有 #toJPQLString(),您可以通过强制转换访问它),但我对此表示怀疑。 即使有可能,我也不认为这是执行此类操作的好代码。我建议找到另一种设计解决方案(为此,您可以指定您遇到的实际问题类型)。例如,如果您正在动态构建查询,则可以使用 JPA Criteria API,并且与“构建”JPA 查询一起,您可以维护反映查询逻辑的内部数据结构。

        【讨论】:

          【解决方案7】:

          使用 Hibernate 作为提供程序,您可以启用以下属性:

          hibernate.show_sql  
          

          将所有 SQL 语句写入控制台。这是将日志类别 org.hibernate.SQL 设置为调试的替代方法。 (例如真|假)

          hibernate.format_sql
          

          在日志和控制台中漂亮地打印 SQL。 (例如真|假)

          或者,如上所述,您可以为记录器启用到调试级别的日志记录

          org.hibernate.SQL
          

          在执行时记录所有 SQL DML 语句

          【讨论】:

            【解决方案8】:
            【解决方案9】:

            您可以使用 p6spy。在以下链接有其操作说明:

            【讨论】:

              猜你喜欢
              • 2017-11-22
              • 1970-01-01
              • 2013-11-10
              • 2015-03-10
              • 1970-01-01
              • 2015-07-30
              • 1970-01-01
              • 2012-07-27
              • 2019-01-26
              相关资源
              最近更新 更多