2009-02-16 14 views
56

¿hay alguna manera fácil de obtener el sql (generado) a partir de un criterio de Hibernate?Cómo obtener SQL de la API de criterios de Hibernate (* no * para el registro)

Lo ideal sería tener algo como:

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) 

La idea sería entonces utilizar el SQL como parte de un 'menos' consulta enorme (Tengo que encontrar las diferencias entre 2 esquemas idénticos - idéntico en estructura, no en los datos - y la desventaja es que no soportado por Hibernate)

(por cierto sé que puedo comprobar el SQL de los archivos de registro)

Respuesta

33

que he hecho algo como esto utilizando Spring AOP por lo que pude tomar los sql, parámetros, errores y tiempo de ejecución para cualquier consulta ejecutar en la aplicación ya sea HQL, Criteria o SQL nativo.

Esto es obviamente frágil, insegura, con sujeción a romper con los cambios en Hibernate, etc, pero ilustra que es posible obtener el 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 toda la cosa en un try/catch y el uso bajo tu propio riesgo.

+0

¿No sería más portátil redirigir temporalmente el registro de hibernación a una cadena? –

+0

Posiblemente, pero si varios subprocesos ejecutan SQL al mismo tiempo, puede ser difícil determinar qué mensajes de registro van con el SQL que está tratando de capturar. Un interceptor que usa onPrepareStatement también le daría el SQL, pero el OP pidió una forma de obtener el SQL para un objeto Criteria dado. –

+1

¿Hay alguna forma de obtener también los parámetros de la consulta SQL impresos? – JRR

35

Aquí es "otra" forma de obtener el 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

Intenté su solución y funciona genial, excepto por una cosa. No se imprime correctamente cuando mi criterio tiene criteria.setMaxResults (n). No filma ese requisito en la declaración generada. ¿Sabes por qué? –

+0

Gracias, me ayudó a depurar una aplicación en la que tuve que trabajar y a encontrar el error de inmediato. – Bevor

+0

Buena respuesta. Lo he adaptado para que se ejecute en una sola línea, por lo que puede ejecutarse fácilmente en una sesión de depuración o agregarse a una lista de observación, etc. Consulte la respuesta a continuación: https://stackoverflow.com/questions/554481#46788621 –

10

Para aquellos que utilizan NHibernate, este es un puerto de código 's [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(); 
    } 
+2

¿sabe usted? cómo obtener los valores de los parámetros en esa consulta? – harishr

7

Si está usando Hibernate 3.6 puede usar el código en la respuesta aceptada (proporcionado por Brian Deterling) con una ligera modificación:

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

Me gusta esto si hormiga para obtener sólo algunas partes de la consulta:

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

Por ejemplo algo como esto:

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

Aquí es un método que utiliza y trabajé para mí

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

Gracias por este código. Sin embargo, hay un pequeño error tipográfico ("traductor" debe ser "traductor"). Y al reemplazar la llamada del constructor de CriteriaLoader con la de @Michael, también funcionará con hibernate 3.6+ (probado con 4.1.9) – creinig

0

Esta respuesta es basado en la respuesta del usuario3715338 (con un pequeño error ortográfico corregido) y mezclado con la respuesta de Michael para Hibernate 3.6, según la respuesta aceptada de Brian Deterling. entonces yo extendí (para PostgreSQL) con un par más tipos sustitución de los 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

Para cualquier persona que desee hacer esto en una sola línea (por ejemplo, en la/ventana Inmediato Display, una expresión de inspección o similar en una sesión de depuración), lo siguiente lo hará e "impresión bonita" el 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()); 

...o aquí está una versión más fácil de leer:

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

Notas:

  1. La respuesta se basa en the solution posted by ramdane.i.
  2. Supone que el objeto Criteria se llama crit. Si recibe un nombre diferente, realice una búsqueda y reemplace.
  3. Asume que la versión de Hibernate es posterior a 3.3.2.GA pero anterior a la 4.0 para usar BasicFormatterImpl para "imprimir bastante" el HQL. Si usa una versión diferente, vea this answer para saber cómo modificar. O tal vez simplemente elimine por completo la bonita impresión, ya que es simplemente un "bueno tener".
  4. Se trata de utilizar getEnabledFilters en lugar de getLoadQueryInfluencers() para la compatibilidad hacia atrás ya que este último se introdujo en una versión posterior de Hibernate (3,5 ???)
  5. no de salida los valores de parámetro real utilizado si la consulta es parametrizada.
Cuestiones relacionadas