如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)
Posted
技术标签:
【中文标题】如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)【英文标题】:How to get SQL from Hibernate Criteria API (*not* for logging) 【发布时间】:2010-10-07 22:57:41 【问题描述】:有没有办法从 Hibernate Criteria
获取(待生成的)SQL?
理想情况下,我会有类似的东西:
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 作为一个巨大的 'MINUS' 查询的一部分(我需要找到 2 个相同模式之间的差异 - 结构相同,而不是数据 - 并且 Hibernate 不支持 MINUS)
(顺便说一句,我知道我可以从日志文件中检查 SQL)
【问题讨论】:
【参考方案1】:这是获取 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();
【讨论】:
我尝试了您的解决方案,除了一件事外,它的效果很好。当我的标准具有criteria.setMaxResults(n) 时,它不能正确打印出来。它没有在生成的语句中满足该要求。你知道为什么吗? 谢谢,帮助我调试了我必须处理的应用程序并立即找到错误。 不错的答案。我已将其调整为在单行中运行,因此可以在调试会话中轻松运行或添加到观察列表等。请参阅下面的答案:***.com/questions/554481#46788621 我的实体关联条件查询与另一个表有 oneTomany 关系。所以 CriteriaQuery.list() 结果是一个联合结果。但是转换后的sql字符串不包含任何加入?【参考方案2】:我使用 Spring AOP 完成了类似的操作,因此我可以获取应用程序中运行的任何查询的 sql、参数、错误和执行时间,无论它是 HQL、Criteria 还是本机 SQL。
这显然是脆弱的、不安全的,可能会随着 Hibernate 等的变化而中断,但它说明了获取 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);
用 try/catch 封装整个东西,使用风险自负。
【讨论】:
暂时将休眠日志重定向到字符串不是更便携吗? 可能,但如果多个线程同时执行 SQL,则可能很难确定哪些日志消息与您尝试捕获的 SQL 一起出现。使用 onPrepareStatement 的拦截器也会为您获取 SQL,但 OP 要求提供一种方法来获取给定 Criteria 对象的 SQL。 有没有办法把SQL查询的参数也打印出来? 这是我获取参数的方法:gist.github.com/bdeterling/5563683。我已经有大约 4 年没有重新访问它了。 如果你使用的是带有 JPA 的 Hibernate 5.x,并且有一个 javax.persistence.Query 实例,这可以工作:return new org.hibernate.engine.jdbc.internal.BasicFormatterImpl().format (query.unwrap(org.hibernate.query.Query.class).getQueryString());【参考方案3】:对于那些使用 NHibernate 的人,这是 [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();
【讨论】:
您知道如何获取该查询中的参数值吗??【参考方案4】:如果您使用的是 Hibernate 3.6,您可以使用已接受答案中的代码(由 Brian 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);
【讨论】:
【参考方案5】:如果您只想获取查询的某些部分,我喜欢这样:
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;
【讨论】:
【参考方案6】:这是我使用并为我工作的一种方法
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 ");
【讨论】:
感谢此代码。然而,有一个小错字(“traslator”应该是“translator”)。当用@Michael 的答案替换 CriteriaLoader 构造函数调用时,它也适用于休眠 3.6+(用 4.1.9 测试)【参考方案7】:对于希望在单行中执行此操作的任何人(例如在显示/立即窗口、调试会话中的监视表达式或类似内容),以下将执行此操作并“漂亮地打印”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()
);
注意事项:
-
答案基于the solution posted by ramdane.i。
它假定Criteria 对象被命名为
crit
。 如果名称不同,请搜索并替换。
假设 Hibernate 版本晚于 3.3.2.GA 但早于 4.0,以便使用 BasicFormatterImpl 来“漂亮地打印”HQL。 如果使用不同的版本,请参阅this answer 了解如何修改。或者也许只是完全删除漂亮的印刷品,因为它只是“很高兴拥有”。
它使用 getEnabledFilters
而不是 getLoadQueryInfluencers()
来实现向后兼容性,因为后者是在更高版本的 Hibernate (3.5???) 中引入的
如果查询是参数化的,它不会输出实际使用的参数值。
【讨论】:
【参考方案8】:此答案基于 user3715338 的答案(纠正了一个小的拼写错误),并与 Michael 对 Hibernate 3.6 的答案混合 - 基于 Brian Deterling 接受的答案。然后我用更多的类型替换了问号(对于 PostgreSQL):
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(">", " > ");
【讨论】:
以上是关于如何从 Hibernate Criteria API 获取 SQL(*not* 用于日志记录)的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JPA Criteria API / Hibernate 按 Case 语句分组