2013-10-01 5 views
0

Я пытаюсь запустить хранимую процедуру «do_build» из спящего режима, и написали вызов таким образом:Запуск Oracle SQL хранимых процедур в HQL

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) 

Я просто хотел, чтобы подтвердить, прежде чем сделать изменения - я должен быть просто queryi ng с 'call do_build', или есть что-то еще потенциально неправильное здесь?

+0

Для этого вам необходимо использовать собственный запрос, насколько я знаю. –

+0

Я использовал createNativeQuery, а также не удалось выполнить «execute do_build» (мне также пришлось удалить Boolean.class) – mathematician

ответ

0

Используйте ключевое слово для вызова. Hibernate не понимает выполнение.

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

Я не уверен в addEntity в сочетании с булевым классом, так как я использую, чтобы вернуть Entity для этого. Но в основном это о createSQLQuery и использовать CALL. Проверьте, помогает ли это. Если не пытаться выполнить выполнение совместно с createSQLQuery.

+0

Я не так привык с JPA/Hibernate, но у меня нет доступа к сеансу в этом случае .. У меня есть EntityManager, который не поддерживает .createSQLQuery .. – mathematician

0
Query query = session.createSQLQuery(
    "CALL procedureName(:parameter)") 
    .addEntity(ClassName.class) 
    .setParameter("parameter", "parameterValue"); 
2

я отвечаю на этот вопрос в надежде, что это поможет вам непосредственно, но и, если нет, то, что другие, которые имели этот дракон Joust с натыкается ответ здесь на SO. Это настоящая doozie по-своему, и вы думаете, что будет очень четкое обсуждение того, как обращаться с такими вещами в каждой основной инфо-палубе Hibernate, но, к сожалению, здесь и там есть только разбросанные биты. намекает на этом пути. После битвы в течение пары дней, чтобы собрать вместе исправление, я нашел, что сработало для меня.

Во-первых, я должен использовать файлы .hbm.xml и сопоставления, а не аннотации. Это связано с отсутствием объявленных ПК в используемых таблицах, а для них ПК (суррогатные ключи, по существу), является фиксированным (это не входит в мой контроль). Аннотации кажутся «счастливыми», если на таблицах объявлены PK. Если нет, вы должны использовать класс идентификации для каждого класса таблицы. (Например: у вас есть таблица: «MyTable». Если вы, например, используете Hib. Reverse Engineering Tools в Eclipse, он будет генерировать для вас два исходных файла: MyTable.java и MyTableId.java (и абстрактные классы тоже, если вы попросите их). MyTableId.java - это тот, который будет содержать фактические значения.)

Я работаю с Hibernate 3.3. Пока мы еще не до 4.x.

И, наконец, используемая база данных - это Oracle. Моя задача состояла в том, чтобы запустить SP, который принял один параметр и не возвратил никаких записей. Это система SP, используемая для привязки произвольного строкового значения к текущему сеансу связи («USERENV»), связанного с ключевым значением «CLIENT_IDENTIFIER». Предполагается, что разработчик должен назначить сеансу идентификационную строку пользователя приложения (например, зарегистрированный идентификатор пользователя), который затем может быть извлечен в триггере или в 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, вы не найдете его на всех в спящем «Session» класса Javadocs.

Это означает, что вам нужно, если вы планируете модернизировать (??) ваша версия Hibernate некоторое время до 4.x и будет содержать такой код, он перестанет работать. Если вы планируете написать что-то новое и не знаете, будете ли вы обновлять или нет, лучше безопасно, чем сожалеть. (Вы не хотите звонить в 3:00 утра, не так ли?)

Первое, на что я столкнулся, когда искал способ запуска SP на Oracle с использованием собственного объекта SQL Query (SQLQuery) или запрос HQL (объект запроса) состоит в том, что у обоих есть проблема с поддержкой только действия выбора или действия обновления: .list() и .executeUpdate(). Нет простого и простого .execute(), как вы находите в других DAL или в java.sql. SP, который я хотел запустить [DBMS_SESSION.SET_IDENTIFIER (userid)] ничего не возвращает. Кроме того, все усилия, которые я предпринял для простого сеанса Hibernate, вызвали строку {CALL DBMS_SESSION.SET_IDENTIFIER (userid)}. Я попытался futzing с синтаксисом утверждения и т. Д. Нет кубиков.

Наконец, после многократного серфинга я понял, что если Hibernate ожидает, что что-то будет возвращено (любое значение) из SP, оно должно рассматриваться как объект базы данных. Это соответствует попытке запустить даже простой, неквалифицированный оператор select, используя Hibernate; если у вас нет аннотированных файлов Java, сопоставляющих данные с таблицами или файлами .hbm.xml, аналогично, Hibernate просто не будет сотрудничать - и это идея. Поэтому мне пришлось придумать способ представления отношения SP к базе данных, даже несмотря на то, что нет таблицы, которая сопоставляется с ней. Дракона нужно было обмануть.

Шаг 1: Создайте файл .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/) Также обратите внимание, что < класс > нет не не обязательно. Это решение не будет работать без него. (Если вы выполните «выбор * из DUAL» во время входа в Oracle, вы получите один столбец с именем «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; 
    } 

} 

Вот и все ! Теперь, если вы используете аннотации, вам придется добавить те, которые подходят.


Update 12/10/13: Пошел к Hib 3.6.3 и теперь можно использовать аннотации OK для этого, поэтому мы угробили .hbm файлы. Двойной.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" /> 

Почти сделано! Все спасено? Отлично.

В любой исходный файл у вас есть, что вы хотите назвать СП с, код будет, по крайней мере, в моем случае, выглядит следующим образом:

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: next at oracle.jdbc.driver.OracleResultSetImpl.next (OracleResultSetImpl.ja va: 240) ...

Обратите внимание на общую тему: Hibernate не может получить запись любого вида. Это не тот вид исключения, о котором вам нужно беспокоиться, если все, что вы делаете, это выполнение SP и не хотите извлекать записи из него.

Как всегда, проверьте и проверьте еще несколько ... убедитесь, что он действительно делает то, что вы хотите. Независимо от того, хотите ли вы регистрировать какие-либо ошибки или просто использовать ошибки, зависит от вас.

НАКОНЕЦ, что бы вы сделали, если, как я, вы хотите использовать DBMS_SESSION.SET_IDENTIFIER() хранимую процедуру в Oracle, так что вы могли бы получить некоторые вошедшие в профиле ID пользователя в базе данных на стороне триггер или SP?Вот база данных на стороне PL/SQL для этого, достаточно просто:

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

В моем конкретном случае, я использую это в качестве триггера, который служит для регистрации любых изменений в определенную таблицу для целей аудита. Но изменения могут происходить из любого места: пользователь настольного SQL * Plus, приложение, которое может или не может использовать процедуру DBMS_SESSION.SET_IDENTIFIER() и т. Д., И, следовательно, не может быть установлено значение CLIENT_IDENTIFIER в USERENV для любого заданного соединение сеанса. Если это так, USERNAME вернется как null. Так что у меня есть этот дополнительный блок сразу же после того, как один выше, который получает идентификатор соединения в случае CLIENT_IDENTIFIER равна нулю:

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

поэтому у меня есть что-то, чтобы поместить в поле идентификатора в таблице аудита.

Выполнено. Надеюсь, это поможет кому-то. Не стесняйтесь комментировать.

Смежные вопросы