0

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

4

3 回答 3

2

我正在回答这个问题,希望它能直接帮助你,但如果没有,其他曾与这条龙较量的人会在 SO 上找到答案。这是一个真正的doozie,你会认为在每个主要的Hibernate信息甲板上都会有一个关于如何处理这类事情的非常清晰的讨论,但不幸的是,这里和那里只有零散的部分和一路上的提示。经过几天的努力拼凑修复后,我找到了对我有用的方法。

首先,我必须使用 .hbm.xml 文件和映射,而不是注释。这是由于在使用的表上缺少声明的 PK,并且为它们制作 PK(实质上是代理键)被禁止(这超出了我的控制)。只有在表格上有声明的 PK 时,注释才会显得“快乐”。如果没有,您必须为每个表类使用一个标识类。(例如:您有一个表:“MyTable”。例如,如果您在 Eclipse 中使用 Hib.Reverse Engineering Tools,它将为您生成两个源文件:MyTable.java 和 MyTableId.java(还有抽象类,如果你要求他们)。MyTableId.java 是保存实际价值相关内容的那个。)

我正在使用休眠 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/ )还要注意 <class> 的东西不是可选的。没有它,此解决方案将无法工作。(如果您在登录 Oracle 时执行“select * from DUAL”,您将返回名为“DUMMY”的一列和返回值为“X”的单个字段的行。因此您需要声明“DUMMY” " 字段,如图所示。而且,不要担心 <generator class="identity" /> 引用,因为您永远不会将任何内容保存到 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,现在可以为此使用注释 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) { }

好的,怎么回事?请注意,命名查询是“callDdbmsSessionSetIdentifier”。这是我用来标记 .hbm.xml 文件中定义的实际查询的内容(见上文;查看 <sql-query name="callDdbmsSessionSetIdentifier"> 元素)。现在,请注意我正在捕获调用 query.list() 并使用它引发的异常。通常,这将是一个巨大的禁忌,对吧?好吧,如果你愿意,你可以举报。如果您想让您的日志不被大量垃圾消息填满,您可以只记录它的消息而不是整个跟踪来报告它。您将获得的例外情况如下:

(日期时间)- 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 语句执行提取:下一个 在 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 字段中。

完毕。我希望这对那里的人有帮助。随意发表评论。

于 2013-11-08T22:12:05.693 回答
0

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

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

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

于 2013-10-01T17:29:33.103 回答
0
Query query = session.createSQLQuery(
    "CALL procedureName(:parameter)")
    .addEntity(ClassName.class)
    .setParameter("parameter", "parameterValue");
于 2013-10-02T11:32:59.470 回答