Come ottenere SQL da Hibernate Criteria API (* not * per la registrazione)

c’è un modo semplice per ottenere il sql (da generare) da un Hibernate Criteria?

Idealmente avrei qualcosa di simile:

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) 

L’idea sarebbe quindi quella di usare sql come parte di un’enorme query “MINUS” (ho bisogno di trovare le differenze tra due schemi identici – identici nella struttura, non nei dati – e il MINUS non è supportato da Hibernate)

(A proposito, so che posso controllare l’SQL dai file di registro)

Ho fatto qualcosa di simile usando Spring AOP in modo da poter afferrare sql, parametri, errori e tempo di esecuzione per qualsiasi query eseguita nell’applicazione, indipendentemente dal fatto che fosse HQL, Criteria o SQL nativo.

Questo è ovviamente fragile, insicuro, sobject a rompere con le modifiche in Hibernate, ecc, ma illustra che è ansible ottenere l’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); 

Avvolgere l’intera cosa in un tentativo / cattura e utilizzare a proprio rischio.

Ecco un “altro” modo per ottenere l’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(); 

Per chi usa NHibernate, questo è un porto del codice di [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(); } 

Se si utilizza Hibernate 3.6, è ansible utilizzare il codice nella risposta accettata (fornita da Brian Deterling) con una leggera modifica:

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

Mi piace questo se vuoi ottenere solo alcune parti della query:

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

Ad esempio qualcosa del genere:

 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; 

Ecco un metodo che ho usato e lavorato per me

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

Questa risposta è basata sulla risposta dell’utente3715338 (con un piccolo errore di ortografia corretto) e miscelata con la risposta di Michael per Hibernate 3.6, basata sulla risposta accettata di Brian Deterling. L’ho poi esteso (per PostgreSQL) con un paio di altri tipi in sostituzione dei punti di domanda:

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

Per chiunque desideri eseguire questa operazione in una singola riga (ad esempio nella finestra Visualizza / Immediata, un’espressione di controllo o simile in una sessione di debug), quanto segue lo farà e “stamperà” l’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 ecco una versione più facile da leggere:

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

Gli appunti:

  1. La risposta è basata sulla soluzione pubblicata da ramdane.i .
  2. Assume che l’object Criteria è denominato crit . Se nominato diversamente, fai una ricerca e sostituisci .
  3. Si presuppone che la versione di Hibernate sia successiva alla 3.3.2.GA ma precedente alla 4.0 per poter utilizzare BasicFormatterImpl per “stampare” l’HQL. Se si utilizza una versione diversa, vedere questa risposta per sapere come modificare. O forse basta rimuovere completamente la bella stampa perché è solo un “bello da avere” .
  4. Sta usando getEnabledFilters piuttosto che getLoadQueryInfluencers() per la retrocompatibilità dal momento che quest’ultimo è stato introdotto in una versione successiva di Hibernate (3.5 ???)
  5. Non emette i valori dei parametri effettivi utilizzati se la query è parametrizzata.