如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)

Posted

技术标签:

【中文标题】如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)【英文标题】:How to get SQL from Hibernate Criteria API (*not* for logging) 【发布时间】:2010-10-07 22:57:41 【问题描述】:

有没有办法从 Hibernate Criteria 获取(待生成的)SQL?

理想情况下,我会有类似的东西:

Criteria criteria = session.createCriteria(Operator.class);

... build up the criteria ...
... and then do something like ...

String sql = criteria.toSql()

(But this of course does not exist)

然后的想法是使用 SQL 作为一个巨大的 'MINUS' 查询的一部分(我需要找到 2 个相同模式之间的差异 - 结构相同,而不是数据 - 并且 Hibernate 不支持 MINUS)

(顺便说一句,我知道我可以从日志文件中检查 SQL)

【问题讨论】:

【参考方案1】:

这是获取 SQL 的“另一种”方式:

CriteriaImpl criteriaImpl = (CriteriaImpl)criteria;
SessionImplementor session = criteriaImpl.getSession();
SessionFactoryImplementor factory = session.getFactory();
CriteriaQueryTranslator translator=new CriteriaQueryTranslator(factory,criteriaImpl,criteriaImpl.getEntityOrClassName(),CriteriaQueryTranslator.ROOT_SQL_ALIAS);
String[] implementors = factory.getImplementors( criteriaImpl.getEntityOrClassName() );

CriteriaJoinWalker walker = new CriteriaJoinWalker((OuterJoinLoadable)factory.getEntityPersister(implementors[0]), 
                        translator,
                        factory, 
                        criteriaImpl, 
                        criteriaImpl.getEntityOrClassName(), 
                        session.getLoadQueryInfluencers()   );

String sql=walker.getSQLString();

【讨论】:

我尝试了您的解决方案,除了一件事外,它的效果很好。当我的标准具有criteria.setMaxResults(n) 时,它不能正确打印出来。它没有在生成的语句中满足该要求。你知道为什么吗? 谢谢,帮助我调试了我必须处理的应用程序并立即找到错误。 不错的答案。我已将其调整为在单行中运行,因此可以在调试会话中轻松运行或添加到观察列表等。请参阅下面的答案:***.com/questions/554481#46788621 我的实体关联条件查询与另一个表有 oneTomany 关系。所以 CriteriaQuery.list() 结果是一个联合结果。但是转换后的sql字符串不包含任何加入?【参考方案2】:

我使用 Spring AOP 完成了类似的操作,因此我可以获取应用程序中运行的任何查询的 sql、参数、错误和执行时间,无论它是 HQL、Criteria 还是本机 SQL。

这显然是脆弱的、不安全的,可能会随着 Hibernate 等的变化而中断,但它说明了获取 SQL 是可能的:

CriteriaImpl c = (CriteriaImpl)query;
SessionImpl s = (SessionImpl)c.getSession();
SessionFactoryImplementor factory = (SessionFactoryImplementor)s.getSessionFactory();
String[] implementors = factory.getImplementors( c.getEntityOrClassName() );
CriteriaLoader loader = new CriteriaLoader((OuterJoinLoadable)factory.getEntityPersister(implementors[0]),
    factory, c, implementors[0], s.getEnabledFilters());
Field f = OuterJoinLoader.class.getDeclaredField("sql");
f.setAccessible(true);
String sql = (String)f.get(loader);

用 try/catch 封装整个东西,使用风险自负。

【讨论】:

暂时将休眠日志重定向到字符串不是更便携吗? 可能,但如果多个线程同时执行 SQL,则可能很难确定哪些日志消息与您尝试捕获的 SQL 一起出现。使用 onPrepareStatement 的拦截器也会为您获取 SQL,但 OP 要求提供一种方法来获取给定 Criteria 对象的 SQL。 有没有办法把SQL查询的参数也打印出来? 这是我获取参数的方法:gist.github.com/bdeterling/5563683。我已经有大约 4 年没有重新访问它了。 如果你使用的是带有 JPA 的 Hibernate 5.x,并且有一个 javax.persistence.Query 实例,这可以工作:return new org.hibernate.engine.jdbc.internal.BasicFormatterImpl().format (query.unwrap(org.hibernate.query.Query.class).getQueryString());【参考方案3】:

对于那些使用 NHibernate 的人,这是 [ram] 代码的一个端口

public static string GenerateSQL(ICriteria criteria)
    
        NHibernate.Impl.CriteriaImpl criteriaImpl = (NHibernate.Impl.CriteriaImpl)criteria;
        NHibernate.Engine.ISessionImplementor session = criteriaImpl.Session;
        NHibernate.Engine.ISessionFactoryImplementor factory = session.Factory;

        NHibernate.Loader.Criteria.CriteriaQueryTranslator translator = 
            new NHibernate.Loader.Criteria.CriteriaQueryTranslator(
                factory, 
                criteriaImpl, 
                criteriaImpl.EntityOrClassName, 
                NHibernate.Loader.Criteria.CriteriaQueryTranslator.RootSqlAlias);

        String[] implementors = factory.GetImplementors(criteriaImpl.EntityOrClassName);

        NHibernate.Loader.Criteria.CriteriaJoinWalker walker = new NHibernate.Loader.Criteria.CriteriaJoinWalker(
            (NHibernate.Persister.Entity.IOuterJoinLoadable)factory.GetEntityPersister(implementors[0]),
                                translator,
                                factory,
                                criteriaImpl,
                                criteriaImpl.EntityOrClassName,
                                session.EnabledFilters);

        return walker.SqlString.ToString();
    

【讨论】:

您知道如何获取该查询中的参数值吗??【参考方案4】:

如果您使用的是 Hibernate 3.6,您可以使用已接受答案中的代码(由 Brian Deterling 提供)稍作修改:

  CriteriaImpl c = (CriteriaImpl) criteria;
  SessionImpl s = (SessionImpl) c.getSession();
  SessionFactoryImplementor factory = (SessionFactoryImplementor) s.getSessionFactory();
  String[] implementors = factory.getImplementors(c.getEntityOrClassName());
  LoadQueryInfluencers lqis = new LoadQueryInfluencers();
  CriteriaLoader loader = new CriteriaLoader((OuterJoinLoadable) factory.getEntityPersister(implementors[0]), factory, c, implementors[0], lqis);
  Field f = OuterJoinLoader.class.getDeclaredField("sql");
  f.setAccessible(true);
  String sql = (String) f.get(loader);

【讨论】:

【参考方案5】:

如果您只想获取查询的某些部分,我喜欢这样:

new CriteriaQueryTranslator(
    factory,
    executableCriteria,
    executableCriteria.getEntityOrClassName(), 
    CriteriaQueryTranslator.ROOT_SQL_ALIAS)
        .getWhereCondition();

例如这样的:

String where = new CriteriaQueryTranslator(
    factory,
    executableCriteria,
    executableCriteria.getEntityOrClassName(), 
    CriteriaQueryTranslator.ROOT_SQL_ALIAS)
        .getWhereCondition();

String sql = "update my_table this_ set this_.status = 0 where " + where;

【讨论】:

【参考方案6】:

这是我使用并为我工作的一种方法

public static String toSql(Session session, Criteria criteria)
    String sql="";
    Object[] parameters = null;
    try
        CriteriaImpl c = (CriteriaImpl) criteria;
        SessionImpl s = (SessionImpl)c.getSession();
        SessionFactoryImplementor factory = (SessionFactoryImplementor)s.getSessionFactory();
        String[] implementors = factory.getImplementors( c.getEntityOrClassName() );
        CriteriaLoader loader = new CriteriaLoader((OuterJoinLoadable)factory.getEntityPersister(implementors[0]), factory, c, implementors[0], s.getEnabledFilters());
        Field f = OuterJoinLoader.class.getDeclaredField("sql");
        f.setAccessible(true);
        sql = (String)f.get(loader);
        Field fp = CriteriaLoader.class.getDeclaredField("traslator");
        fp.setAccessible(true);
        CriteriaQueryTranslator translator = (CriteriaQueryTranslator) fp.get(loader);
        parameters = translator.getQueryParameters().getPositionalParameterValues();
    
    catch(Exception e)
        throw new RuntimeException(e);
    
    if (sql !=null)
        int fromPosition = sql.indexOf(" from ");
        sql = "SELECT * "+ sql.substring(fromPosition);

        if (parameters!=null && parameters.length>0)
            for (Object val : parameters) 
                String value="%";
                if(val instanceof Boolean)
                    value = ((Boolean)val)?"1":"0";
                else if (val instanceof String)
                    value = "'"+val+"'";
                
                sql = sql.replaceFirst("\\?", value);
            
        
    
    return sql.replaceAll("left outer join", "\nleft outer join").replace(" and ", "\nand ").replace(" on ", "\non ");

【讨论】:

感谢此代码。然而,有一个小错字(“traslator”应该是“translator”)。当用@Michael 的答案替换 CriteriaLoader 构造函数调用时,它也适用于休眠 3.6+(用 4.1.9 测试)【参考方案7】:

对于希望在单行中执行此操作的任何人(例如在显示/立即窗口、调试会话中的监视表达式或类似内容),以下将执行此操作并“漂亮地打印”SQL:

new org.hibernate.jdbc.util.BasicFormatterImpl().format((new org.hibernate.loader.criteria.CriteriaJoinWalker((org.hibernate.persister.entity.OuterJoinLoadable)((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory().getEntityPersister(((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory().getImplementors(((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName())[0]),new org.hibernate.loader.criteria.CriteriaQueryTranslator(((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory(),((org.hibernate.impl.CriteriaImpl)crit),((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName(),org.hibernate.loader.criteria.CriteriaQueryTranslator.ROOT_SQL_ALIAS),((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory(),(org.hibernate.impl.CriteriaImpl)crit,((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName(),((org.hibernate.impl.CriteriaImpl)crit).getSession().getEnabledFilters())).getSQLString());

...或者这里有一个更容易阅读的版本:

new org.hibernate.jdbc.util.BasicFormatterImpl().format(
  (new org.hibernate.loader.criteria.CriteriaJoinWalker(
     (org.hibernate.persister.entity.OuterJoinLoadable)
      ((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory().getEntityPersister(
        ((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory().getImplementors(
          ((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName())[0]),
     new org.hibernate.loader.criteria.CriteriaQueryTranslator(
          ((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory(),
          ((org.hibernate.impl.CriteriaImpl)crit),
          ((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName(),
          org.hibernate.loader.criteria.CriteriaQueryTranslator.ROOT_SQL_ALIAS),
     ((org.hibernate.impl.CriteriaImpl)crit).getSession().getFactory(),
     (org.hibernate.impl.CriteriaImpl)crit,
     ((org.hibernate.impl.CriteriaImpl)crit).getEntityOrClassName(),
     ((org.hibernate.impl.CriteriaImpl)crit).getSession().getEnabledFilters()
   )
  ).getSQLString()
);

注意事项:

    答案基于the solution posted by ramdane.i。 它假定Criteria 对象被命名为crit如果名称不同,请搜索并替换。 假设 Hibernate 版本晚于 3.3.2.GA 但早于 4.0,以便使用 BasicFormatterImpl 来“漂亮地打印”HQL。 如果使用不同的版本,请参阅this answer 了解如何修改。或者也许只是完全删除漂亮的印刷品,因为它只是“很高兴拥有”。 它使用 getEnabledFilters 而不是 getLoadQueryInfluencers() 来实现向后兼容性,因为后者是在更高版本的 Hibernate (3.5???) 中引入的 如果查询是参数化的,它不会输出实际使用的参数值。

【讨论】:

【参考方案8】:

此答案基于 user3715338 的答案(纠正了一个小的拼写错误),并与 Michael 对 Hibernate 3.6 的答案混合 - 基于 Brian Deterling 接受的答案。然后我用更多的类型替换了问号(对于 PostgreSQL):

public static String toSql(Criteria criteria)

    String sql = "";
    Object[] parameters = null;
    try
    
        CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
        SessionImpl sessionImpl = (SessionImpl) criteriaImpl.getSession();
        SessionFactoryImplementor factory = sessionImpl.getSessionFactory();
        String[] implementors = factory.getImplementors(criteriaImpl.getEntityOrClassName());
        OuterJoinLoadable persister = (OuterJoinLoadable) factory.getEntityPersister(implementors[0]);
        LoadQueryInfluencers loadQueryInfluencers = new LoadQueryInfluencers();
        CriteriaLoader loader = new CriteriaLoader(persister, factory,
            criteriaImpl, implementors[0].toString(), loadQueryInfluencers);
        Field f = OuterJoinLoader.class.getDeclaredField("sql");
        f.setAccessible(true);
        sql = (String) f.get(loader);
        Field fp = CriteriaLoader.class.getDeclaredField("translator");
        fp.setAccessible(true);
        CriteriaQueryTranslator translator = (CriteriaQueryTranslator) fp.get(loader);
        parameters = translator.getQueryParameters().getPositionalParameterValues();
    
    catch (Exception e)
    
        throw new RuntimeException(e);
    
    if (sql != null)
    
        int fromPosition = sql.indexOf(" from ");
        sql = "\nSELECT * " + sql.substring(fromPosition);

        if (parameters != null && parameters.length > 0)
        
            for (Object val : parameters)
            
                String value = "%";
                if (val instanceof Boolean)
                
                    value = ((Boolean) val) ? "1" : "0";
                
                else if (val instanceof String)
                
                    value = "'" + val + "'";
                
                else if (val instanceof Number)
                
                    value = val.toString();
                
                else if (val instanceof Class)
                
                    value = "'" + ((Class) val).getCanonicalName() + "'";
                
                else if (val instanceof Date)
                
                    SimpleDateFormat sdf = new SimpleDateFormat(
                        "yyyy-MM-dd HH:mm:ss.SSS");
                    value = "'" + sdf.format((Date) val) + "'";
                
                else if (val instanceof Enum)
                
                    value = "" + ((Enum) val).ordinal();
                
                else
                
                    value = val.toString();
                
                sql = sql.replaceFirst("\\?", value);
            
        
    
    return sql.replaceAll("left outer join", "\nleft outer join").replaceAll(
        " and ", "\nand ").replaceAll(" on ", "\non ").replaceAll("<>",
        "!=").replaceAll("<", " < ").replaceAll(">", " > ");

【讨论】:

以上是关于如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)的主要内容,如果未能解决你的问题,请参考以下文章

hibernate-criteria查询

Hibernate中Criteria的完整用法

如何使用 Hibernate Criteria 编写此查询

如何使用 JPA Criteria API / Hibernate 按 Case 语句分组

查询后如何设置@Transient 字段值?使用 Hibernate Criteria 进行查询

如何使用 Hibernate Criteria 连接两个具有 OneToMany 关系的表