我试图从Hibernate运行存储过程'do_build',并以这种方式编写调用:
this.entityManager.createQuery("execute do_build", Boolean.class)
但我得到以下例外
01 Oct 2013 15:15:00,058 [ERROR] (schedulerFactoryBean_Worker-1) org.hibernate.hql.PARSER: line 1:1: unexpected token: execute
和
java.lang.IllegalArgumentException: node to traverse cannot be null!
at org.hibernate.hql.ast.util.NodeTraverser.traverseDepthFirst(NodeTraverser.java:63)
at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:280)
at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:182)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:101)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:98)
at org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:156)
at org.hibernate.impl.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:135)
at org.hibernate.impl.SessionImpl.createQuery(SessionImpl.java:1760)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:277)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
at com.sun.proxy.$Proxy37.createQuery(Unknown Source)
我只是想在做出改变之前确认一下 - 我应该简单地用'call do_build'来查询,或者这里有其他可能的错误吗?
答案 0 :(得分:2)
我正在回答这个问题,希望它可以直接帮助你,但如果没有,那些曾经让这条龙与之争斗过的人会在这里遇到答案。这是一个真正的doozie以你自己的方式,你认为在每个主要的Hibernate信息卡片中都有一个非常明确的讨论如何处理这类事情,但不幸的是,只有分散的位这里和那里以及沿途的提示。经过几天的努力拼凑修复后,我找到了对我有用的东西。
首先,我必须使用.hbm.xml文件和映射,而不是注释。这是因为在使用的表上缺少声明的PK,并且为它们制作PK(实质上是替代键)(这是我无法控制的)。注释只看起来很开心&#34;如果表上有声明的PK。如果没有,则必须为每个表类使用标识类。 (例如:你有一个表:&#34; MyTable&#34;。例如,如果你在Eclipse中使用Hib。逆向工程工具,它将为你生成两个源文件:MyTable.java和MyTableId.java(和抽象类,如果你要求它们。)MyTableId.java将保存与实际价值相关的东西。)
我正在使用Hibernate 3.3。我们还没达到4.x.
最后,正在使用的数据库是Oracle。我的挑战是运行一个带有一个参数并且没有返回记录的SP。它是一个系统SP,用于将任意字符串值绑定到与键值“CLIENT_IDENTIFIER”相关联的当前连接会话(&#39; USERENV&#39;)。这应该允许开发者向会话分配应用程序用户的身份字符串(例如,登录的用户ID),然后可以在数据库侧的触发器或SP中提取该字符串。提取是容易的部分;它让Hibernate让你运行这个难点的SP。
过去,你可以抓住基本的Oracle连接并通过它运行对SP的调用而不用大张旗鼓。看起来像这样:
String userid = "<something from someplace>";
Session session = getSession(); // whatever way you get your Hib. session.
/* 'WSCallHelper' as used below is a helper class found in the IBM Websphere API
programmer library. In some other context, a programmer would use a different
means to isolate the Oracle-native connection. */
OracleConnection oracleconnection =
( OracleConnection ) WSCallHelper.getNativeConnection( session.connection() );
CallableStatement st = oracleconnection.
prepareCall( "{call DBMS_SESSION.SET_IDENTIFIER(:userid)}");
st.setString( "userid", userid );
st.execute();
现在问题:在3.3中不推荐使用session.connection(),在最近的4.x中,你不会在Hibernate&#34; Session&#34;中找到它。 Javadocs类。
这意味着你必须,如果你计划将你的Hibernate版本升级(??)到4.x并且有这种代码,它就会停止工作。如果你打算写一些新东西并且不知道你是否会升级 - 比抱歉更安全。 (你不想要凌晨3点的电话,对吗?我也不是。)
在寻找使用本机SQL查询对象(SQLQuery)或HQL查询(查询对象)在Oracle上运行SP的方法时,我遇到的第一件事就是它们都只支持选择操作的问题或更新操作:.list()和.executeUpdate()。没有像在其他DAL或java.sql中找到的简单.execute()。我想要运行的SP [DBMS_SESSION.SET_IDENTIFIER( userid )]不返回任何内容。此外,我所做的只是将Hibernate会话交给字符串{CALL DBMS_SESSION.SET_IDENTIFIER( userid )}的所有努力都失败了。我尝试使用语句语法等等。没有骰子。
最后经过大量的浏览后,我意识到如果Hibernate希望从SP返回某些东西(任何类型的值),它必须被视为某种类型的数据库实体。这与尝试使用Hibernate运行一个简单的,不合格的select语句一致;如果没有带注释的Java文件将数据映射到表或.hbm.xml文件同样如此,那么Hibernate根本就不会合作 - 这就是这个想法。因此,我必须想出一种方法来表示SP与数据库的关系,即使没有映射到它的表。龙需要被欺骗。
步骤1:为Dual创建一个.hbm.xml文件(是的,Oracle中的伪表),但前提是你没有使用注释。它应该看起来很像这样,根据需要/需要修改您要运行的包结构,查询名称和实际SP:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.company.hibernate.dataaccess.model.Dual" table="DUAL">
<id name="dummy" type="java.lang.String">
<column name="DUMMY" />
<generator class="identity" />
</id>
</class>
<sql-query name="callDdbmsSessionSetIdentifier">
<return alias="dummy" class="com.company.hibernate.dataaccess.model.Dual"/>
<![CDATA[CALL DBMS_SESSION.SET_IDENTIFIER(:userid)]]>
</sql-query>
</hibernate-mapping>
请注意:对DBMS_SESSION.SET_IDENTIFIER的调用是在CDATA块中,因为应该是这样的任何其他CALL。 (我有Mkyong感谢这个暗示:http://www.mkyong.com/hibernate/hibernate-named-query-examples/) 另请注意&lt; class&gt;东西是不可选。没有它,这个解决方案将无法运作。 (如果您执行&#34;从DUAL&#34中选择*;在登录Oracle时,您将获得一列名为&#34; DUMMY&#34;并且返回一行,其中一行返回值为&#的单个字段34; X&#34;。所以你需要声明如图所示的&#34; DUMMY&#34;字段。并且,不要担心&lt; generator class =&#34; identity&#34; /&gt; ;引用,因为你永远不会将任何东西保存到DUAL。)
如果您正在使用注释,请在&#34; Dual.java&#34;中使用与上述相同的注释。文件,无论您使用注释还是.hbm.xml映射,都是您需要的。接下来讨论该文件。添加对hibernate.cfg.xml文件的正确引用将是最后一步。
第2步:创建&#34; Dual.java&#34;文件 如上所示,例如,我们假设一个com.company.hibernate.dataaccess.model包:
package com.company.hibernate.dataaccess.model;
public class Dual implements java.io.Serializable {
private String dummy = "";
public void setDummy (String s) {
dummy = s;
}
public String getDummy () {
return dummy;
}
}
那就是它!现在,如果您正在使用注释,则必须添加适合的注释。
更新12/10/13: 转到Hib 3.6.3,现在可以使用注释OK,所以我们抛弃了.hbm文件。现在使用的Dual.java文件如下所示:
package {whatever};
import javax.persistence.*; // Better to name each entity, but using * for brevity
import org.hibernate.annotations.NamedNativeQueries;
import org.hibernate.annotations.NamedNativeQuery;
@NamedNativeQueries({
@NamedNativeQuery(
name = "callDdbmsSessionSetIdentifier",
query = "CALL DBMS_SESSION.SET_IDENTIFIER(:userid)",
resultClass = Dual.class
)
})
@Entity
@Table( name = "DUAL", schema = "SYS" )
public class Dual implements java.io.Serializable {
private DualId id;
public Dual() {
}
public Dual( DualId id ) {
this.id = id;
}
@EmbeddedId
@AttributeOverrides( {
@AttributeOverride( name = "dummy", column = @Column( name = "DUMMY", nullable = false, length = 1 ) ),
} )
public DualId getId() {
return this.id;
}
public void setId( DualId id ) {
this.id = id;
}
}
如果您关心,DualId.java看起来像这样:
package {whatever};
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class DualId implements java.io.Serializable {
private String dummy;
public DualId() {
}
public DualId( String dummy ) {
this.dummy = dummy;
}
// Bean compliance only; 'DUMMY' can't be changed in DUAL and
// why you'd care to get it when you know already it's just an "X",
// dunno. But these get.. and set.. methods are needed anyway.
@Column( name = "DUMMY", nullable = false, length = 1 )
public String getDummy() {
return this.dummy;
}
public void setDummy( String dummy ) {
}
public boolean equals( Object other ) {
if ( ( this == other ) )
return true;
if ( ( other == null ) )
return false;
if ( !( other instanceof DualId ) )
return false;
DualId castOther = ( DualId ) other;
return ((this.getDummy() == castOther.getDummy() ) || ( this.getDummy() != null && castOther.getDummy() != null && this
.getDummy().equals( castOther.getDummy())));
}
public int hashCode() {
int result = 17;
result = 37 * result + ( getDummy() == null ? 0 : this.getDummy().hashCode() );
return result;
}
}
第3步:更新hibernate.cfg.xml文件
将此行添加到映射资源条目列表中,如果使用.hbm.xml文件,调整路径或包引用以适应:
<mapping resource="com/company/hibernate/hbm/Dual.hbm.xml" />
如果使用注释,请添加以下行:
<mapping class="com.company.hibernate.dataaccess.model.Dual" />
差不多完成了!一切都救了?大。
在你想要从中调用SP的任何源文件中,代码至少在我的情况下会如下所示:
Session session = getSession(); // somehow...
String userid = "<got this someplace>";
Query query = session.getNamedQuery( "callDdbmsSessionSetIdentifier" ).
setParameter( "userid", userid );
try {
query.list();
} catch (Exception e) { }
好的,发生了什么?请注意,命名查询是&#39; callDdbmsSessionSetIdentifier&#39;。这就是我用来标记.hbm.xml文件中定义的实际查询的内容(参见上文;查看&lt; sql-query name =&#34; callDdbmsSessionSetIdentifier&#34;&gt;元素)。现在,请注意我通过调用query.list()并使用它来捕获抛出的异常。通常情况下,这是一个巨大的禁忌,对吧?好吧,如果你愿意,你可以报告。如果您希望防止日志填满大量垃圾邮件,则可以通过仅记录其消息而不是整个跟踪来报告它。您将获得的例外情况如下:
(date-time) - JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions 无法对PLSQL语句执行fetch:next ... (date-time) - SystemErr R org.hibernate.exception.GenericJDBCException:无法执行查询 在org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:82) ... 引起:java.sql.SQLException:无法对PLSQL语句执行fetch:next at oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java:240) ... 的
请注意常见的主题:Hibernate无法获得任何类型的记录。如果您所做的只是执行SP并且不想从中检索记录,那么您不必担心这种异常。
与往常一样,测试和测试更多......确保它确实按照您的意愿行事。您是否想要进行任何类型的错误记录或只是消耗错误取决于您。
最终 ,如果像我一样想要在Oracle中使用DBMS_SESSION.SET_IDENTIFIER()存储过程,那么您可以做些什么呢?用户在数据库端触发器或SP中的用户ID?这里是数据库端的PL / SQL,很简单:
USERNAME VARCHAR2(50) := NULL;
...
select SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')
INTO USERNAME
from DUAL;
...
在我的特定情况下,我在触发器中使用它,用于记录对某个表的任何更改以用于审计目的。但是更改可能来自任何地方:SQL * Plus的桌面用户,可能使用或不使用DBMS_SESSION.SET_IDENTIFIER()过程的应用程序等,因此在任何给定的USERENV中可能没有为CLIENT_IDENTIFIER设置值会话连接。如果是这种情况,USERNAME将返回null。因此,如果CLIENT_IDENTIFIER为null,我会在上面的一个块之后获得连接ID:
IF USERNAME IS NULL THEN
SELECT USER INTO USERNAME FROM DUAL;
END IF;
所以我有一些东西要放在审计表的ID字段中。
完成。我希望这对那里的人有所帮助。随意评论。
答案 1 :(得分:0)
使用call关键字。 Hibernate不理解执行。
Query query = session.createSQLQuery(
"CALL do_build()")
.addEntity(Boolean.class);
我不确定addEntity与boolean类一起使用,因为我用它来返回一个Entity。但基本上它是关于createSQLQuery并使用CALL。看看这是否有帮助。如果没有尝试与createSQLQuery一起使用Execute。
答案 2 :(得分:0)
Query query = session.createSQLQuery(
"CALL procedureName(:parameter)")
.addEntity(ClassName.class)
.setParameter("parameter", "parameterValue");