java - 在HQL中运行Oracle SQL存储过程

标签 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”进行查询,还是这里可能存在其他错误?

最佳答案

我正在回答这个问题,希望它可以直接为您提供帮助,但也可以帮助您,让其他曾与这条龙争吵的人会在此找到答案。这本身就是一个真正的笨蛋,您可能会认为在每个主要的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/
还要注意,东西不是可选的。没有它,该解决方案将无法工作。 (如果登录到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;
    }

}


而已!现在,如果您使用的是注释,则必须添加相应的注释。



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

(日期时间)-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/

相关文章:

java - WeakHashMap 还是 HashMap?

java - 如何使用crawler4j提取页面上的所有链接?

java - 如何将枚举与父类(super class)型分组

sql - 选择行并根据列的值删除重复项

mysql - 如何在票证支持系统中获取单张票证的所有评论?

spring - 从 JBoss EAP 6.1 中排除 JPA 子系统 - 尝试在 JBoss EAP 6.1 中使用 JPA 2.1

java - 攻击者可以将 sql 或 javascript 注入(inject) java POJO 中吗?

java - 按下 JButton 后多次生成 JPanel "blink"

java - 如何将来自 openapi-generator 的客户端包含在 gradle java 应用程序中?

sql - SQL Server 中总计的运行摘要