在HQL中运行Oracle SQL存储过程

时间:2013-10-01 15:37:15

标签: java sql hibernate exception hql

我试图从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'来查询,或者这里有其他可能的错误吗?

3 个答案:

答案 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");