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で答えに出くわすことを願っています. これはそれ自体が本当におかしな話であり、すべての主要な Hibernate 情報デッキでこの種のことを正確に処理する方法について非常に明確な議論があると思うかもしれませんが、残念ながら、あちこちに散らばったビットしかありません。途中でヒント。修正をまとめるために数日間苦労した後、私は自分に合ったものを見つけました。

まず、アノテーションではなく、.hbm.xml ファイルとマッピングを使用する必要があります。これは、使用中のテーブルで宣言された PK が不足しているためであり、それらの PK (本質的には代理キー) を作成することはできません (これは私の制御範囲外です)。注釈は、テーブルに宣言された PK がある場合にのみ「幸せ」に見えます。そうでない場合は、テーブル クラスごとに ID クラスを使用する必要があります。(例: 「MyTable」というテーブルがあります。たとえば、Eclipse で Hib. リバース エンジニアリング ツールを使用すると、MyTable.java と MyTableId.java (および抽象クラス) の 2 つのソース ファイルが生成されます。 MyTableId.java は、実際の値関連のものを保持するものです。)

私はHibernate 3.3で作業しています。まだ 4.x に達していません。

最後に、使用中のデータベースは Oracle です。私の課題は、1 つのパラメーターを取り、レコードを返さない SP を実行することでした。これはシステム SP であり、キー値「CLIENT_IDENTIFIER」に関連付けられた現在の接続セッション (「USERENV」) に任意の文字列値をバインドするために使用されます。これにより、開発者は、データベース側のトリガーまたは SP で抽出できる、アプリケーション ユーザーの ID 文字列 (たとえば、ログインしているユーザー ID) をセッションに割り当てることができるようになります。その抽出は簡単な部分です。難しい部分であるこのSPを実行できるようにするためにHibernateを取得しています。

以前は、ベースの 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」クラスの Javadoc でまったく見つかりません。

これは、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 とデータベースとの関係を表す方法を考え出す必要がありました。ただし、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 への呼び出しは、このような他の CALL と同様に、CDATA ブロックにあります。(このヒントに感謝する Mkyong があります: http://www.mkyong.com/hibernate/hibernate-named-query-examples/ ) また、 <class> はオプションではないことに注意してください。このソリューションは、それなしでは機能しません。(Oracle にログインしているときに「select * from DUAL」を実行すると、「DUMMY」という名前の 1 つの列が返され、「X」の値を保持する単一のフィールドを含む 1 つの行が返されます。したがって、「DUMMY を宣言する必要があります。また、DUAL には何も保存しないため、<generator class="identity" /> 参照について心配する必要はありません。)

注釈を使用している場合は、「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 ファイルで定義された実際のクエリにラベルを付けるために使用したものです (上記を参照してください。<sql-query name="callDdbmsSessionSetIdentifier"> 要素を見てください)。ここで、 query.list() を呼び出してそれを消費することによってスローされた例外をキャッチしていることに注意してください。通常、これは大したことではありませんよね?まあ、気が向いたら通報していいよ。大量のジャンク メッセージでログがいっぱいになるのを防ぎたい場合は、トレース全体ではなくメッセージをログに記録するだけで報告できます。あなたが得る例外は次のようなものになります:

(日時) - JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions PLSQLステートメントでフェッチを実行できません: 次 ... (日時) - SystemErr R org.hibernate.exception.GenericJDBCException: 組織でクエリを実行できませんでした.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:82) ... 原因: java.sql.SQLException: PLSQLステートメントでフェッチを実行できません: next at oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java :240) ...

共通のテーマに注意してください: Hibernate はどのような種類のレコードも取得できません。これは、SP を実行するだけで、SP からレコードを取得したくない場合は、心配する必要がある種類の例外ではありません。

いつものように、テストとテストを繰り返します...実際に意図したとおりに動作していることを確認してください。何らかのエラー ログを記録するか、単にエラーを消費するかはユーザー次第です。

最後に、私のように、データベース側のトリガーまたは SP でログインしているユーザーのユーザー ID を取得できるように、Oracle でDBMS_SESSION.SET_IDENTIFIER () ストアド プロシージャを使用したい場合はどうしますか? そのためのデータベース側の PL/SQL は次のとおりです。

USERNAME VARCHAR2(50) := NULL;
...
select SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')
    INTO USERNAME
from DUAL;
...

私の特定のケースでは、監査目的で特定のテーブルへの変更を記録するトリガーでこれを使用します。ただし、SQL*Plus のデスクトップ ユーザー、DBMS_SESSION.SET_IDENTIFIER() プロシージャを使用する場合と使用しない場合があるアプリケーションなど、どこからでも変更が加えられる可能性があります。セッション接続。その場合、USERNAME は null として返されます。したがって、CLIENT_IDENTIFIER が null の場合に接続 ID を取得する上記のブロックの直後に、この追加のブロックがあります。

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

そのため、監査テーブルの ID フィールドに入力するものがあります。

終わり。これが誰かの助けになることを願っています。お気軽にコメントください。

于 2013-11-08T22:12:05.693 に答える
0
Query query = session.createSQLQuery(
    "CALL procedureName(:parameter)")
    .addEntity(ClassName.class)
    .setParameter("parameter", "parameterValue");
于 2013-10-02T11:32:59.470 に答える