2009-02-16 8 views
56

есть ли простой способ получить (сгенерированный) sql из Hibernate Criteria?Как получить SQL из Hibernate Criteria API (* not * для ведения журнала)

В идеале я хотел бы иметь что-то вроде:

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 как часть огромного минуса запроса (мне нужно найти различие между 2 идентичными схемами - идентичной в структура, а не в данных - и MINUS не поддерживается Hibernate)

(кстати, я знаю, я могу проверить SQL из лог-файлов)

ответ

33

Я сделал что-то вроде этого с помощью Spring AOP, так что я мог захватить sql, параметры, ошибки и время выполнения для любого запроса запускается в приложении, будь то HQL, Criteria или собственный SQL.

Это, очевидно, хрупкое, небезопасно, при условии разрыва с изменениями в спящем режиме, и т.д., но это показывает, что это возможно, чтобы получить 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); 

Wrap всей вещь в Try/улове и использовании на свой страх и риск.

+0

Не было бы более портативным переадресация журнала спящего режима на строку временно? –

+0

Возможно, но если несколько потоков выполняли SQL в одно и то же время, может быть сложно определить, какие сообщения журнала идут с SQL, который вы пытаетесь захватить. Перехватчик, использующий onPrepareStatement, также предоставит вам SQL, но OP попросил способ получить SQL для данного объекта Criteria. –

+1

Есть ли способ получить параметры SQL-запроса, распечатанные? – JRR

35

Вот "другой" способ получить 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(); 
+0

Я пробовал ваше решение, и оно отлично работает, за исключением одной вещи. Он не распечатывается правильно, когда у моих критериев есть критерии. SetMaxResults (n). Он не снимает это требование в сгенерированном выражении. Ты знаешь почему? –

+0

Спасибо, помог мне отладить приложение, над которым мне приходилось работать, и немедленно найти ошибку. – Bevor

+0

Хороший ответ. Я адаптировал его для работы в одной строке, поэтому его можно легко запустить в сеансе отладки или добавить в список наблюдения и т. Д. См. Ответ ниже: https://stackoverflow.com/questions/554481#46788621 –

10

Для тех, кто использует NHibernate, это порт [RAM] код 's

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(); 
    } 
+2

Знаете ли вы как получить значения параметров в этом запросе? – harishr

7

Если вы используете Hibernate 3.6 вы можете использовать код в принятом ответе (при условии, Брайан 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); 
4

Мне нравится это, если вы ш муравей получить только некоторые части запроса:

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; 
3

Вот метод, который я использовал и работал для меня

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 "); 
} 
+0

Спасибо за этот код. Однако есть немного опечаток («трассатор» должен быть «переводчиком»). И при замене вызова конструктора CriteriaLoader с ответом от ответа @Michael он также будет работать с hibernate 3.6+ (проверен с 4.1.9) – creinig

0

Этот ответ основанный на ответе пользователя3715338 (с небольшой исправленной орфографической ошибкой) и смешанный с ответом Майкла для Hibernate 3.6 - на основе принятого ответа Брайана Детерлинга. Затем я расширил его (для PostgreSQL) с еще парой типов замены questionmarks:

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(">", " > "); 
} 
0

Для тех, кто хочет сделать это в одной строке (например, в Display/открывшемся окне, выражении часов или аналогичном в debug session), следующее будет делать это и «довольно печатать» 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() 
); 

Примечания:

  1. Ответ основан на the solution posted by ramdane.i.
  2. Предполагается, что объект Criteria имеет имя crit. Если имя по-разному, выполните поиск и замените.
  3. Предполагается, что версия Hibernate позже 3.3.2.GA, но раньше 4.0, чтобы использовать BasicFormatterImpl для «довольно печатной» версии HQL. Если вы используете другую версию, см. this answer о том, как изменить. Или, возможно, просто удалите красивую печать целиком, так как это просто «приятно иметь».
  4. Она использует getEnabledFilters, а не getLoadQueryInfluencers() для обратной совместимости, поскольку последний был введен в более поздней версии Hibernate (3.5 ???)
  5. Это не выводит фактические значения параметров, используемые, если запрос спараметрирован.