【问题标题】:Running Oracle SQL Stored Procedure in HQL在 HQL 中运行 Oracle SQL 存储过程
【发布时间】:2013-10-01 15:37:15
【问题描述】:

我正在尝试从 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”进行查询,还是这里有其他潜在问题?

【问题讨论】:

  • 据我所知,您需要为此使用本机查询。
  • 我使用了 createNativeQuery,但也因“执行 do_build”而失败(我还必须删除 Boolean.class)

标签: java sql hibernate exception hql


【解决方案1】:

我正在回答这个问题,希望它能直接帮助你,但如果没有,其他曾与这条龙较量的人会在 SO 上找到答案。这是一个真正的doozie,你会认为在每个主要的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) ] 什么也不返回。此外,我为将字符串 {CALL DBMS_SESSION.SET_IDENTIFIER(userid)} 交给 Hibernate 会话所做的所有努力都失败了。我尝试过使用语句语法等。没有骰子。

最后,经过大量的浏览,我意识到如果 Hibernate 期望从 SP 中返回一些东西(任何类型的值),那么它必须被视为某种数据库实体。这与尝试使用 Hibernate 运行一个简单的、不合格的选择语句是一致的;如果您没有带注释的 Java 文件将数据映射到表或 .hbm.xml 文件,那么 Hibernate 根本不会合作——这就是想法。所以我必须想出一种方法来表示 SP 与数据库的关系,即使没有映射到它的表。龙需要被欺骗。

第 1 步:为 Dual(是的,Oracle 中的伪表)创建一个 .hbm.xml 文件,但前提是您不使用注释。它应该看起来很像这样,根据需要/需要,针对您想要运行的包结构、查询名称和实际 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 块中,应该是任何其他类似这样的调用。 (我要感谢 Mkyong 的提示:http://www.mkyong.com/hibernate/hibernate-named-query-examples/) 还要注意 的东西是 not 可选的。没有它,此解决方案将无法工作。 (如果您在登录 Oracle 时执行“select * from DUAL”,您将返回名为“DUMMY”的一列和返回值为“X”的单个字段的行。因此您需要声明“DUMMY” " 字段,如图所示。而且,不要担心 引用,因为您永远不会将任何内容保存到 DUAL。)

如果您使用注解,请在“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;
    }

}

就是这样!现在,如果您使用注释,则必须添加它们以适应。


2013 年 12 月 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 文件中定义的实际查询的内容(见上文;查看 元素)。现在,请注意我正在捕获调用 query.list() 并使用它引发的异常。通常,这将是一个巨大的禁忌,对吧?好吧,如果你愿意,你可以举报。如果您想让您的日志不被大量垃圾消息填满,您可以只记录它的消息而不是整个跟踪来报告它。您将得到的例外情况如下:

(date-time) - JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions 无法对 PLSQL 语句执行提取:下一步 ... (日期时间)- SystemErr R org.hibernate.exception.GenericJDBCException:无法执行查询 在 org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:82) ... 原因:java.sql.SQLException:无法对 PLSQL 语句执行 fetch:下一个 在 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 为空的情况下,我在上面获取连接 ID 的块之后有这个额外的块:

IF USERNAME IS NULL THEN
   SELECT USER INTO USERNAME FROM DUAL;
END IF;

所以我有一些东西要放在审计表的 ID 字段中。

完成。我希望这对那里的人有帮助。欢迎发表评论。

【讨论】:

    【解决方案2】:

    使用 call 关键字。 Hibernate 不理解执行。

    Query query = session.createSQLQuery( "CALL do_build()") .addEntity(Boolean.class);

    我不确定 addEntity 是否与布尔类结合使用,因为我曾经为此返回一个实体。但基本上它是关于 createSQLQuery 和使用 CALL。看看这是否有帮助。如果不尝试结合 createSQLQuery 执行。

    【讨论】:

    • 我不太熟悉 JPA/Hibernate,但在这种情况下我无法访问会话。我有一个不支持 .createSQLQuery 的 EntityManager ..
    【解决方案3】:
    Query query = session.createSQLQuery(
        "CALL procedureName(:parameter)")
        .addEntity(ClassName.class)
        .setParameter("parameter", "parameterValue");
    

    【讨论】:

      猜你喜欢
      • 2013-12-09
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 2011-03-09
      • 1970-01-01
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多