我试图从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”进行查询,还是这里可能存在其他错误?
最佳答案
我正在回答这个问题,希望它可以直接为您提供帮助,但也可以帮助您,让其他曾与这条龙争吵的人会在此找到答案。这本身就是一个真正的笨蛋,您可能会认为在每个主要的Hibernate信息平台中都会有关于如何处理这类事情的非常清晰的讨论,但是不幸的是,到处都是零散的地方,一路提示。经过几天的努力拼凑解决方案后,我发现了对我有用的方法。
首先,我必须使用.hbm.xml文件和映射,而不是注释。这是由于在使用的表上缺少声明的PK,而使它们的PK(实质上是代理键)被淘汰了(这是我无法控制的)。仅当表上已声明PK时,注释才显得“高兴”。如果不是,则必须为每个表类使用一个标识类。 (例如:您有一个表:“ MyTable”。例如,如果您在Eclipse中使用Hib。Reverse Engineering Tools,它将为您生成两个源文件:MyTable.java和MyTableId.java(以及抽象类,如果需要的话。MyTableId.java是保存实际值相关内容的文件。)
我正在使用Hibernate 3.3。我们还没有达到4.x。
最后,使用的数据库是Oracle。我的挑战是运行一个带有一个参数并且不返回任何记录的SP。它是一个系统SP,用于将任意字符串值绑定到与键值“ CLIENT_IDENTIFIER”关联的当前连接会话(“ USERENV”)。这应该允许开发人员向会话分配应用程序用户的标识字符串(例如,已登录的用户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();
现在出现的问题:session.connection()在3.3中已弃用,而在最新的4.x中,您根本不会在Hibernate的“ Session”类Javadocs中找到它。
这意味着,如果您计划将Hibernate版本升级(?)到4.x,并且必须放置这种代码,它将不得不停止工作。如果您打算编写新内容而又不知道是否要进行升级,那最好的办法莫过于后悔。 (您不希望凌晨3:00通话,对吗?我也不想。)
寻找使用本机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/)
还要注意,
如果使用批注,请在“ Dual.java”文件中执行与上述等效的批注,无论使用批注或.hbm.xml映射,都将需要此批注。接下来讨论该文件。向您的hibernate.cfg.xml文件添加正确的引用将是最后一步。
第2步:创建“ Dual.java”文件
如上所示,出于示例目的,我们假设使用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;
}
}
而已!现在,如果您使用的是注释,则必须添加相应的注释。
更新13年10月10日:
转到Hib 3.6.3,现在可以使用注释“确定”,因此我们放弃了.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) { }
好,怎么回事?注意,命名查询为“ callDdbmsSessionSetIdentifier”。这就是我用来标记.hbm.xml文件中定义的实际查询的内容(请参见上文;查看
(日期时间)-JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions无法对PLSQL语句执行获取:next
...
(日期时间)-SystemErr R org.hibernate.exception.GenericJDBCException:无法执行查询
在org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:82)
...
原因:java.sql.SQLException:无法对PLSQL语句执行获取:next
在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将返回为空。因此,在CLIENT_IDENTIFIER为null的情况下,在获得连接ID的上述代码之后,我后面有一个额外的块:
IF USERNAME IS NULL THEN
SELECT USER INTO USERNAME FROM DUAL;
END IF;
所以我要在审核表的ID字段中添加一些内容。
做完了我希望这对外面的人有帮助。随时发表评论。
关于java - 在HQL中运行Oracle SQL存储过程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19120393/