So erhalten Sie SQL von der Hibernate Criteria API (* nicht * für die Protokollierung)

73

Gibt es eine Möglichkeit, die (zu generierende) SQL aus einem Ruhezustand abzurufen Criteria?

Im Idealfall hätte ich so etwas wie:

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)

Die Idee wäre dann, das SQL als Teil einer riesigen 'MINUS'-Abfrage zu verwenden (ich muss die Unterschiede zwischen zwei identischen Schemas finden - identisch in der Struktur, nicht in den Daten - und das MINUS wird von Hibernate nicht unterstützt).

(Übrigens weiß ich, dass ich die SQL aus den Protokolldateien überprüfen kann)

David Bulté
quelle

Antworten:

38

Ich habe so etwas mit Spring AOP gemacht, damit ich die SQL, Parameter, Fehler und Ausführungszeit für jede Abfrage in der Anwendung abrufen kann, unabhängig davon, ob es sich um HQL, Kriterien oder natives SQL handelt.

Dies ist offensichtlich fragil, unsicher und kann durch Änderungen im Ruhezustand usw. unterbrochen werden. Es zeigt jedoch, dass es möglich ist, SQL abzurufen:

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

Wickeln Sie das Ganze in einen Versuch / Fang und verwenden Sie es auf eigenes Risiko.

Brian Deterling
quelle
Wäre es nicht portabler, das Protokoll für den Ruhezustand vorübergehend in eine Zeichenfolge umzuleiten?
Elazar Leibovich
Möglicherweise, aber wenn mehrere Threads gleichzeitig SQL ausführen, ist es möglicherweise schwierig herauszufinden, welche Protokollnachrichten zu dem SQL gehören, das Sie erfassen möchten. Ein Interceptor, der onPrepareStatement verwendet, würde Ihnen auch die SQL abrufen, aber das OP fragte nach einer Möglichkeit, die SQL für ein bestimmtes Criteria-Objekt abzurufen.
Brian Deterling
1
Gibt es eine Möglichkeit, auch die Parameter der SQL-Abfrage auszudrucken?
JRR
Hier ist meine Methode, um die Parameter abzurufen : gist.github.com/bdeterling/5563683 . Ich habe es seit ungefähr 4 Jahren nicht mehr besucht.
Brian Deterling
1
Wenn Sie Hibernate 5.x mit JPA verwenden und über eine javax.persistence.Query-Instanz verfügen, funktioniert dies wie folgt: Geben Sie das neue Format org.hibernate.engine.jdbc.internal.BasicFormatterImpl (). (Query.unwrap (org.hibernate) zurück .query.Query.class) .getQueryString ());
Archie
43

Hier ist "ein anderer" Weg, um SQL zu erhalten:

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();
ramdane.i
quelle
1
Ich habe Ihre Lösung ausprobiert und sie funktioniert bis auf eine Sache hervorragend. Es wird nicht richtig gedruckt, wenn meine Kriterien Kriterien haben. SetMaxResults (n). Diese Anforderung wird in der generierten Anweisung nicht berücksichtigt. Weißt du, warum?
Sean Nguyen
Vielen Dank, hat mir geholfen, eine Anwendung zu debuggen, an der ich arbeiten musste, und den Fehler sofort zu finden.
Bevor
Gute Antwort. Ich habe dies so angepasst, dass es in einer einzelnen Zeile ausgeführt werden kann, damit es problemlos in einer Debug-Sitzung ausgeführt oder einer Beobachtungsliste usw. hinzugefügt werden kann. Siehe Antwort unten: stackoverflow.com/questions/554481#46788621
Steve Chambers
Die mit meiner Entität verknüpfte Kriterienabfrage hat eine oneTomany-Beziehung zu einer anderen Tabelle. Das Ergebnis von CriteriaQuery.list () ist also ein verbundenes Ergebnis. Aber konvertierte SQL-Zeichenfolge enthält keine Verknüpfung?
GokulRaj KN
11

Für Benutzer von NHibernate ist dies ein Port des Codes von [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();
    }
LiamV
quelle
2
Wissen Sie, wie Sie Werte von Parametern in dieser Abfrage erhalten?
Harishr
7

Wenn Sie Hibernate 3.6 verwenden, können Sie den Code in der akzeptierten Antwort (bereitgestellt von Brian Deterling) mit geringfügigen Änderungen verwenden:

  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);
Michael
quelle
4

Ich mag dies, wenn Sie nur einige Teile der Abfrage erhalten möchten:

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

Zum Beispiel so etwas:

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

Hier ist eine Methode, die ich verwendet und für mich gearbeitet habe

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 ");
}
fformigli
quelle
Danke für diesen Code. Es gibt jedoch einen kleinen Tippfehler ("Übersetzer" sollte "Übersetzer" sein). Und wenn der CriteriaLoader-Konstruktoraufruf durch den aus @Michaels Antwort ersetzt wird, funktioniert er auch mit dem Ruhezustand 3.6+ (getestet mit 4.1.9)
creinig
1

Für alle, die dies in einer einzelnen Zeile tun möchten (z. B. im Anzeige- / Sofortfenster, einem Überwachungsausdruck oder ähnlichem in einer Debug-Sitzung), wird Folgendes ausgeführt und die SQL "hübsch gedruckt":

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

... oder hier ist eine leichter zu lesende Version:

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

Anmerkungen:

  1. Die Antwort basiert auf der von ramdane.i veröffentlichten Lösung .
  2. Es wird davon ausgegangen, dass das Criteria- Objekt benannt ist crit. Wenn anders benannt, suchen und ersetzen .
  3. Es wird davon ausgegangen, dass die Hibernate-Version später als 3.3.2.GA, aber früher als 4.0 ist, um BasicFormatterImpl zum "hübschen Drucken" der HQL zu verwenden. Wenn Sie eine andere Version verwenden, finden Sie in dieser Antwort Informationen zum Ändern. Oder entfernen Sie den hübschen Druck einfach ganz, da er nur "schön zu haben" ist .
  4. Es wird getEnabledFilterseher verwendet als getLoadQueryInfluencers()aus Gründen der Abwärtskompatibilität, da letzteres in einer späteren Version von Hibernate (3.5 ???) eingeführt wurde.
  5. Die tatsächlich verwendeten Parameterwerte werden nicht ausgegeben, wenn die Abfrage parametrisiert ist.
Steve Chambers
quelle
0

Diese Antwort basiert auf der Antwort von user3715338 (mit einem kleinen korrigierten Rechtschreibfehler) und wird mit Michaels Antwort für Hibernate 3.6 gemischt - basierend auf der akzeptierten Antwort von Brian Deterling. Ich habe es dann (für PostgreSQL) um ein paar weitere Typen erweitert, die die Fragezeichen ersetzen:

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(">", " > ");
}
Michael Capper
quelle