jueves, 28 de mayo de 2009

Obtener la SQL generada por una Criteria de Hibernate en runtime

Recientemente he necesitado obtener la consulta SQL que genera una Criteria de Hibernate en tiempo de ejecución. La primera opción, obviamente, es leer la documentación de Hibernate Criteria, pero Hibernate no posee ninguna funcionalidad para obtener la SQL en su clase Criteria, ni en las clases relacionadas. Esto me resulto extraño, dado que con Hibernate Criteria se pueden volcar las consultas al log usando por ejemplo log4j, para ello hay que:
  • Asignar el valor "true" a la propiedad "hibernate.show_sql" en el sessionFactory.
  • Añadir en el fichero de configuración del log4j dos appenders:
    1. org.hibernate.SQL = DEBUG --> Para ver las consultas.
    2. org.hibernate.type = TRACE --> Para ver los valores de los parámetros.
Dado que la información está almacenada dentro de la Criteria, tan solo es necesario "sacarla", y para ello usaremos la introspección de Java. El código para obtener la consulta SQL con sus parámetros es el siguiente:

Como vemos en el código usamos la introspección para inspeccionar el contenido de algunos atributos privados del Loader y así obtener la información que queremos.

Tengo que decir que esta forma de utilizar las criterias e Hibernate no es muy ortodoxa, y que en caso de usarse debe hacerse de forma muy controlada, no debe ser una práctica habitual, ya que nada nos asegura el correcto funcionamiento de este tipo de artificios. Eso si por una necesidad imperiosa necesitamos obtener la consulta SQL que genera en tiempo de ejecución es un alternativa.

3 comentarios:

  1. Bueno, me ha costado un poquillo, pero ya me he copiado el código. La proxima vez, en lugar de una imagen pon el texto para que se pueda copiar y pegar.

    ResponderEliminar
  2. Los nuevos ya se están añadiendo como texto. Pero de todas formas siempre viene bien escribirlo uno mismo para comprenderlo e ir aprendiendo como funciona la cosa internamente.

    Saludos.

    ResponderEliminar
  3. posteo el codigo que adapte para que no sea necesario volver a tipear
    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 ");
    }

    ResponderEliminar