Глава 18. Нативный SQL

Оглавление
  1. 18.1. Использование SQLQuery
    1. 18.1.1. Скалярные запросы
    2. 18.1.2. Запросы сущностей
    3. 18.1.3. Обработка ассоциаций и коллекций
    4. 18.1.4. Возвращение нескольких сущностей
    5. 18.1.5. Возвращение неуправляемых сущностей
    6. 18.1.6. Обработка наследования
    7. 18.1.7. Параметры
  2. 18.2. Именованные SQL-запросы
    1. 18.2.1. Использование return-property для явного указания имён столбцов/псевдонимов
    2. 18.2.2. Использование хранимых процедур для запросов
  3. 18.3. Пользовательский SQL для создания, обновления и удаления
  4. 18.4. Пользовательский SQL для загрузки

Вы также можете выражать запросы на нативном диалекте SQL вашей базы данных. Это полезно, если вы хотите использовать специфичные для базы данных функции, такие как подсказки запросов или ключевое слово CONNECT в Oracle. Это обеспечивает чистую миграцию из прямого приложения, основанного на SQL/JDBC, в Hibernate.

Hibernate позволяет указать рукописный SQL, включая хранимые процедуры, для всех операций создания, обновления, удаления и загрузки.

18.1. Использование SQLQuery

Выполнение нативных SQL-запросов контролируется через интерфейс SQLQuery, который получается путем вызова Session.createSQLQuery(). В следующих разделах описывается, как использовать этот API для запросов.

18.1.1. Скалярные запросы

Самый простой SQL-запрос — получить список скаляров (значений).

sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();

Они возвратят массив объектов List Object (Object[]) со скалярными значениями для каждого столбца в таблице CATS. Hibernate будет использовать ResultSetMetadata для вывода фактического порядка и типов возвращаемых скалярных значений.

Чтобы избежать накладных расходов от использования ResultSetMetadata или просто быть более явным в том, что возвращается, можно использовать addScalar():

sess.createSQLQuery("SELECT * FROM CATS")
 .addScalar("ID", Hibernate.LONG)
 .addScalar("NAME", Hibernate.STRING)
 .addScalar("BIRTHDATE", Hibernate.DATE)

Этот запрос установил:

Он вернет массивы Object, но теперь он не будет использовать ResultSetMetadata, но вместо этого явно получит столбец ID, NAME и BIRTHDATE как Long, String и Short из набора результатов. Это также означает, что будут возвращены только эти три столбца, даже если запрос использует * и может возвращать больше трех столбцов.

Можно оставить информацию о типе для всех или некоторых скаляров.

sess.createSQLQuery("SELECT * FROM CATS")
 .addScalar("ID", Hibernate.LONG)
 .addScalar("NAME")
 .addScalar("BIRTHDATE")

Это по сути тот же запрос, что и раньше, но теперь ResultSetMetaData используется для определения типа NAME и BIRTHDATE, когда тип ID явно указан.

Как java.sql.Types, возвращаемые из ResultSetMetaData, отображённые на типы Hibernate, управляются Dialect? Если определённый тип не отображается или не приводит к ожидаемому типу, его можно настроить с помощью вызовов registerHibernateType в Dialect.

18.1.2. Запросы сущностей

Вышеупомянутые запросы касались возврата скалярных значений, в основном возвращающих «исходные (raw)» значения из набора результатов. Ниже показано, как получить объекты сущности из нативного SQL-запроса с помощью addEntity().

Этот запрос установил:

Предполагая, что Cat отображается как класс с идентификаторами столбцов, NAME и BIRTHDATE, указанные выше запросы будут возвращать List, в котором каждый элемент является сущностью Cat.

Если сущность отображается как «многие-к-одному» к другой сущности, она также должна возвращать её при выполнении нативного запроса, иначе возникнет ошибка базы данных «столбец не найден». Дополнительные столбцы будут автоматически возвращены при использовании нотации *, но мы предпочитаем быть явными, как в следующем примере «много-к-одному» для Dog:

sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);

Это позволит cat.getDog() функционировать должным образом.

18.1.3. Обработка ассоциаций и коллекций

Возможным является join в Dog, чтобы избежать возможного дополнительного обратного перехода для инициализации прокси. Это делается с помощью метода addJoin(), который позволяет вам присоединиться к ассоциации или коллекции.

sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")
 .addEntity("cat", Cat.class)
 .addJoin("cat.dog");

В этом примере возвращаемые Cat’ будут иметь свойство dog полностью инициализированым без дополнительной отправки в базу данных. Обратите внимание, что вы добавили псевдоним («cat»), чтобы указать путь целевого свойства соединения. Можно выполнить одно и то же соединение для коллекций, например. если бы у Cat была связь «один-ко-многим» с Dog.

sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID")
 .addEntity("cat", Cat.class)
 .addJoin("cat.dogs");

На этом этапе вы достигаете пределов того, что возможно с помощью нативных SQL-запросов, не приступая к расширению запросов sql, чтобы сделать их пригодными для использования в Hibernate. Проблемы могут возникать при возврате нескольких сущностей одного и того же типа или когда недостаточно имени псевдонима/столбца по умолчанию.

18.1.4. Возвращение нескольких сущностей

До сих пор предполагается, что имена столбцов набора результатов совпадают с именами столбцов, указанными в документе отображения. Это может быть проблематично для SQL-запросов, которые соединяют несколько таблиц, поскольку одни и те же имена столбцов могут быть в нескольких таблицах.

Внедрение псевдонима столбца необходимо в следующем запросе (который, скорее всего, упадёт):

sess.createSQLQuery("SELECT c.*, m.*  FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
 .addEntity("cat", Cat.class)
 .addEntity("mother", Cat.class)

Запрос был предназначен для возврата двух экземпляров Cat в каждой записи: Cat и ее мать. Запрос упадёт, потому что существует конфликт имен; экземпляры отображаются с теми же именами столбцов. Кроме того, в некоторых базах данных возвращённые псевдонимы столбцов, скорее всего, будут иметь форму «c.ID», «c.NAME» и т. д., которые не равны столбцам, указанным в отображаениях («ID» и «NAME»).

Следующая форма не уязвима при дублировании имени столбца:

sess.createSQLQuery("SELECT {cat.*}, {m.*}  FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID")
 .addEntity("cat", Cat.class)
 .addEntity("mother", Cat.class)

Этот запрос установил:

Обозначения {cat. *} И {mother. *}, используемые выше, являются сокращением для «все свойства». Кроме того, вы можете явно указывать столбцы, но даже в этом случае Hibernate вводит псевдонимы столбцов SQL для каждого свойства. Заполнитель для псевдонима столбца — это просто имя свойства, присвоенное псевдонимом таблицы. В следующем примере вы извлекаете Cat и их mother из другой таблицы (cat_log) в у, которая указана в метаданных отображения. Вы даже можете использовать псевдонимы свойств в секции where.

String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
         "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
         "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql) .addEntity("cat", Cat.class) .addEntity("mother", Cat.class).list()

18.1.4.1. Псевдоним и ссылки на свойства

В большинстве случаев вышеуказанное внедрение псевдонима необходимо. Для запросов, связанных с более сложными отображениями, такими как составные свойства, дискриминаторы наследования, коллекции и т. д., вы можете использовать конкретные псевдонимы, которые позволяют Hibernate внедрять правильные псевдонимы.

В следующей таблице показаны различные способы внедрения псевдонимов. Обратите внимание, что имена псевдонимов в результате являются просто примерами; каждый псевдоним будет иметь уникальное и, возможно, другое имя при использовании.

Таблица 18.1. Псевдонимы внедряемых имён

Описание Синтаксис Пример
Простое свойство {[aliasname].[propertyname]} A_NAME as {item.name}
Составное свойство {[aliasname].[componentname].[propertyname]} CURRENCY as {item.amount.currency},
VALUE as {item.amount.value}
Дискриминатор сущности {[aliasname].class} DISC as {item.class}
Все свойства сущности {[aliasname].*} {item.*}
Ключ коллекции {[aliasname].key} ORGID as {coll.key}
Идентификатор коллекции {[aliasname].id} EMPID as {coll.id}
Элемент коллекции {[aliasname].element} XID as {coll.element}
Свойство элемента в коллекции {[aliasname].element.[propertyname]} NAME as {coll.element.name}
Все свойства элемента в коллекции {[aliasname].element.*} {coll.element.*}
Все свойства коллекции {[aliasname].*} {coll.*}

18.1.5. Возвращение неуправляемых сущностей

Можно применить ResultTransformer к нативным SQL-запросам, позволяя ему возвращать неуправляемые сущности.

sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
        .setResultTransformer(Transformers.aliasToBean(CatDTO.class))

Этот запрос установил:

Вышеупомянутый запрос вернет список CatDTO, который был создан, и ввёл значения NAME и BIRTHNAME в соответствующие им свойства или поля.

18.1.6. Обработка наследования

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

18.1.7. Параметры

Нативные SQL-запросы поддерживают позиционные, а также именованные параметры:

Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
List pusList = query.setString(0, "Pus%").list();
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class); List pusList = query.setString("name", "Pus%").list();

18.2. Именованные SQL-запросы

Именованные SQL-запросы также могут быть определены в документе отображения и вызываться точно так же, как и именованный HQL-запрос (см. раздел 11.4.1.7. «Внешние именованные запросы»). В этом случае вам не нужно вызывать addEntity().

Пример 18.1. Именованный SQL-запрос с использованием элемента отображения <sql-query>

<sql-query name="persons">
    <return alias="person" class="eg.Person"/>
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex}
    FROM PERSON person
    WHERE person.NAME LIKE :namePattern
</sql-query>

Пример 18.2. Выполнение именованного запроса

List people = sess.getNamedQuery("persons")
    .setString("namePattern", namePattern)
    .setMaxResults(50)
    .list();

Элемент <return-join> используется для объединения ассоциаций, а элемент <load-collection> используется для определения запросов, которые инициализируют коллекции.

Пример 18.3. Именованный sql-запрос с ассоциацией

<sql-query name="personsWith">
    <return alias="person" class="eg.Person"/>
    <return-join alias="address" property="person.mailingAddress"/>
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex},
           address.STREET AS {address.street},
           address.CITY AS {address.city},
           address.STATE AS {address.state},
           address.ZIP AS {address.zip}
    FROM PERSON person
    JOIN ADDRESS address
        ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
    WHERE person.NAME LIKE :namePattern
</sql-query>

Именованный SQL-запрос может возвращать скалярное значение. Вы должны объявить псевдоним столбца и тип Hibernate с помощью элемента <return-scalar>:

Пример 18.4. Именованный запрос, возвращающий скаляр

<sql-query name="mySqlQuery">
    <return-scalar column="name" type="string"/>
    <return-scalar column="age" type="long"/>
    SELECT p.NAME AS name, 
           p.AGE AS age,
    FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query>

Вы можете воплотить (в конкретную форму) информацию об отображении набора результатов в элементе <resultset>, который позволит вам либо повторно использовать их в нескольких именованных запросах, либо через набор API ResultSetMapping().

Пример 18.5. <resultset> отображение, используемое для воплощения информации об отображения

<resultset name="personAddress">
    <return alias="person" class="eg.Person"/>
    <return-join alias="address" property="person.mailingAddress"/>
</resultset>
<sql-query name="personsWith" resultset-ref="personAddress"> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex}, address.STREET AS {address.street}, address.CITY AS {address.city}, address.STATE AS {address.state}, address.ZIP AS {address.zip} FROM PERSON person JOIN ADDRESS address ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' WHERE person.NAME LIKE :namePattern </sql-query>

Вы также можете использовать информацию отображения resultset в своих hbm-файлах непосредственно в java-коде.

Пример 18.6. Программное указание информации о отображении результатов

List cats = sess.createSQLQuery(
        "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
    )
    .setResultSetMapping("catAndKitten")
    .list();

До сих пор мы рассматривали только воплощение SQL-запросов с использованием файлов отображения Hibernate. Эта же концепция также доступна с аннотациями и называется именованными нативными запросами. Вы можете использовать @NamedNativeQuery(@NamedNativeQueries) в сочетании с @SqlResultSetMapping(@SqlResultSetMappings). Подобно @NamedQuery, @NamedNativeQuery и @SqlResultSetMapping могут быть определены на уровне класса, но их область действия глобальна для приложения. Давайте посмотрим на примеры.

Пример 18.7 «Именованный SQL-запрос с использованием @NamedNativeQuery вместе с @SqlResultSetMapping» показывает, как параметр resultSetMapping определён в @NamedNativeQuery. Он представляет собой имя определённого @SqlResultSetMapping. Отображение набора результатов объявляет сущности, получаемые этим нативным запросом. Каждое поле сущности привязано к псевдониму SQL (или имени столбца). Все поля сущности, включая подклассы и столбцы внешнего ключа связанных сущностей, должны присутствовать в SQL-запросе. Определения полей являются необязательными, если они отображаются на то же имя столбца, что и объявленное в свойстве класса. В примере возвращаются 2 сущности Night и Area, и каждое свойство объявляется и ассоциируется с именем столбца, фактически имя столбца, полученное запросом.

В примере 18.8 «Неявное отображение набора результатов» отображение набора результатов неявное. Мы описываем только класс сущности отображения набора результатов. Отображение свойств/столбцов выполняется с использованием значений отображения сущности. В этом случае свойство модели привязывается к столбцу model_txt.

Наконец, если ассоциация связанной сущности включает составной первичный ключ, для каждого столбца внешнего ключа должен использоваться элемент @FieldResult. Имя @FieldResult состоит из имени свойства для отношения, за которым следует точка («.»), За которой следует имя или поле или свойство первичного ключа. Это можно увидеть в  примере 18.9 «Использование точечной нотации в @FieldResult для указания ассоциаций» .

Пример 18.7. Именованный SQL-запрос с использованием @NamedNativeQuery вместе с @SqlResultSetMapping

@NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "
    + " night.night_date, area.id aid, night.area_id, area.name "
    + "from Night night, Area area where night.area_id = area.id", 
                  resultSetMapping="joinMapping")
@SqlResultSetMapping(name="joinMapping", entities={
    @EntityResult(entityClass=Night.class, fields = {
        @FieldResult(name="id", column="nid"),
        @FieldResult(name="duration", column="night_duration"),
        @FieldResult(name="date", column="night_date"),
        @FieldResult(name="area", column="area_id"),
        discriminatorColumn="disc"
    }),
    @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {
        @FieldResult(name="id", column="aid"),
        @FieldResult(name="name", column="name")
    })
    }
)

Пример 18.8. Неявное отображение набора результатов

@Entity
@SqlResultSetMapping(name="implicit",
                     entities=@EntityResult(entityClass=SpaceShip.class))
@NamedNativeQuery(name="implicitSample", 
                  query="select * from SpaceShip", 
                  resultSetMapping="implicit")
public class SpaceShip {
    private String name;
    private String model;
    private double speed;
@Id public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Column(name="model_txt") public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; } }

Пример 18.9. Использование точечной нотации в @FieldResult для указания ассоциаций

@Entity
@SqlResultSetMapping(name="compositekey",
        entities=@EntityResult(entityClass=SpaceShip.class,
            fields = {
                    @FieldResult(name="name", column = "name"),
                    @FieldResult(name="model", column = "model"),
                    @FieldResult(name="speed", column = "speed"),
                    @FieldResult(name="captain.firstname", column = "firstn"),
                    @FieldResult(name="captain.lastname", column = "lastn"),
                    @FieldResult(name="dimensions.length", column = "length"),
                    @FieldResult(name="dimensions.width", column = "width")
                    }),
        columns = { @ColumnResult(name = "surface"),
                    @ColumnResult(name = "volume") } )
@NamedNativeQuery(name="compositekey", query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip", resultSetMapping="compositekey") } ) public class SpaceShip { private String name; private String model; private double speed; private Captain captain; private Dimensions dimensions;
@Id public String getName() { return name; }
public void setName(String name) { this.name = name; }
@ManyToOne(fetch= FetchType.LAZY) @JoinColumns( { @JoinColumn(name="fname", referencedColumnName = "firstname"), @JoinColumn(name="lname", referencedColumnName = "lastname") } ) public Captain getCaptain() { return captain; }
public void setCaptain(Captain captain) { this.captain = captain; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; }
public Dimensions getDimensions() { return dimensions; }
public void setDimensions(Dimensions dimensions) { this.dimensions = dimensions; } }
@Entity @IdClass(Identity.class) public class Captain implements Serializable { private String firstname; private String lastname;
@Id public String getFirstname() { return firstname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
@Id public String getLastname() { return lastname; }
public void setLastname(String lastname) { this.lastname = lastname; } }

Совет

Если вы извлекаете единственную сущность с использованием отображения по умолчанию, вы можете указать атрибут resultClass вместо resultSetMapping:

@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class)
public class SpaceShip {

В некоторых нативных запросах вам придется возвращать скалярные значения, например, при построении запросов к отчетам. Вы можете отобразить их в @SqlResultsetMapping через @ColumnResult. Фактически вы можете даже смешивать, сущности и скаляры, возвращая их в одном и том же нативном запросе (это, вероятно, не так уж и важно).

Пример 18.10. Скалярные значения через @ColumnResult

@SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))
@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar")

Другая подсказка, специфичная для нативных запросов: org.hibernate.callable, который может быть истинным или ложным в зависимости от того, является ли запрос хранимой процедурой или нет.

18.2.1. Использование return-property для явного указания имён столбцов/псевдонимов

С <return-property> вы можете явно указать Hibernate, какие псевдонимы столбцов использовать, вместо использования синтаксиса фигурных скобок {}, чтобы позволить Hibernate вводить собственные псевдонимы. Например:

<sql-query name="mySqlQuery">
    <return alias="person" class="eg.Person">
        <return-property name="name" column="myName"/>
        <return-property name="age" column="myAge"/>
        <return-property name="sex" column="mySex"/>
    </return>
    SELECT person.NAME AS myName,
           person.AGE AS myAge,
           person.SEX AS mySex,
    FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>

<return-property> также работает с несколькими столбцами. Это решает ограничение с синтаксисом фигурных скобок {}, который не может позволить микроконтроль свойств нескольких столбцов.

<sql-query name="organizationCurrentEmployments">
    <return alias="emp" class="Employment">
        <return-property name="salary">
            <return-column name="VALUE"/>
            <return-column name="CURRENCY"/>
        </return-property>
        <return-property name="endDate" column="myEndDate"/>
    </return>
        SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
        STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
        REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
        FROM EMPLOYMENT
        WHERE EMPLOYER = :id AND ENDDATE IS NULL
        ORDER BY STARTDATE ASC
</sql-query>

В этом примере <return-property> использовался в комбинации с синтаксисом фигурных скобок {} для внедрения. Это позволяет пользователям выбирать, как они хотят ссылаться на столбцы и свойства.

Если ваше отображение имеет дискриминатор, вы должны использовать <return-discriminator>, чтобы указать столбец дискриминатора.

18.2.2. Использование хранимых процедур для запросов

Hibernate обеспечивает поддержку запросов через хранимые процедуры и функции. Большая часть следующей документации эквивалентна для обоих. Хранимая процедура/функция должна возвращать набор результатов в качестве первого параметра для работы с Hibernate. Пример такой хранимой функции в Oracle 9 и выше выглядит следующим образом:

CREATE OR REPLACE FUNCTION selectAllEmployments
    RETURN SYS_REFCURSOR
AS
    st_cursor SYS_REFCURSOR;
BEGIN
    OPEN st_cursor FOR
 SELECT EMPLOYEE, EMPLOYER,
 STARTDATE, ENDDATE,
 REGIONCODE, EID, VALUE, CURRENCY
 FROM EMPLOYMENT;
      RETURN  st_cursor;
 END;

Чтобы использовать этот запрос в Hibernate, вам необходимо отобразить его с помощью именованного запроса.

<sql-query name="selectAllEmployees_SP" callable="true">
    <return alias="emp" class="Employment">
        <return-property name="employee" column="EMPLOYEE"/>
        <return-property name="employer" column="EMPLOYER"/>
        <return-property name="startDate" column="STARTDATE"/>
        <return-property name="endDate" column="ENDDATE"/>
        <return-property name="regionCode" column="REGIONCODE"/>
        <return-property name="id" column="EID"/>
        <return-property name="salary">
            <return-column name="VALUE"/>
            <return-column name="CURRENCY"/>
        </return-property>
    </return>
    { ? = call selectAllEmployments() }
</sql-query>

Хранимые процедуры в настоящее время возвращают только скаляры и сущности. <return-join> и <load-collection> не поддерживаются.

18.2.2.1. Правила/ограничения использования хранимых процедур

Вы не можете использовать хранимые процедуры с Hibernate, если не соблюдаете некоторые правила процедур/функций. Если они не следуют этим правилам, они не могут использоваться в Hibernate. Если вы все еще хотите использовать эти процедуры, вы должны выполнять их через session.connection(). Правила для каждой базы данных различны, поскольку поставщики баз данных имеют различную семантику/синтаксис хранимых процедур.

Запросы хранимой процедуры не могут быть выгружены с помощью setFirstResult()/ setMaxResults().

Рекомендуемая форма вызова — стандартный SQL92: {? = call functionName (<parameters>)} или {? = call procedureName (<parameters>}. Синтаксис нативного вызова не поддерживается.

Для Oracle применяются следующие правила:

Для сервера Sybase или MS SQL применяются следующие правила:

18.3. Пользовательский SQL для создания, обновления и удаления

Hibernate может использовать собственный SQL для создания, обновления и удаления. SQL может быть переопределен на уровне инструкций или на отдельном уровне столбца. В этом разделе описываются переопределения операторов. Для столбцов см. Раздел 5.6. «Преобразователи столбцов: выражения для чтения и записи» . Пример 18.11. «Пользовательский CRUD через аннотации» показывает, как определить пользовательские операторы SQL с помощью аннотаций.

Примере 18.11. Пользовательский CRUD через аннотации

@Entity
@Table(name="CHAOS")
@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)")
@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?")
@SQLDelete( sql="DELETE CHAOS WHERE id = ?")
@SQLDeleteAll( sql="DELETE CHAOS")
@Loader(namedQuery = "chaos")
@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where xml:id= ?", resultClass = Chaos.class)
public class Chaos {
    @Id
    private Long id;
    private Long size;
    private String name;
    private String nickname;

@SQLInsert, @SQLUpdate, @SQLDelete, @SQLDeleteAll соответственно переопределяют инструкцию INSERT, UPDATE, DELETE и DELETE. То же самое можно сделать с помощью файлов отображения Hibernate и узлов <sql-insert>, <sql-update> и <sql-delete>. Это можно увидеть в примере 18.12 «Пользовательский CRUD XML».

Примере 18.12. Пользовательский CRUD XML

<class name="Person">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name" not-null="true"/>
    <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
    <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE xml:id=?</sql-update>
    <sql-delete>DELETE FROM PERSON WHERE xml:id=?</sql-delete>
</class>

Если вы ожидаете вызвать хранимую процедуру, обязательно установите для атрибута callable значение true. В аннотациях, также же как и в xml.

Чтобы проверить правильность выполнения, Hibernate позволяет определить одну из трех стратегий:

Чтобы определить стиль проверки результата, используйте параметр check, который снова доступен как в аннотации, так и в xml.

Вы можете использовать один и тот же набор аннотаций, соответственно узлов xml, чтобы переопределить связанные с коллекцией утверждения — см. Пример 18.13 «Переопределение операторов SQL для коллекций с использованием аннотаций» .

Примере 18.13. Переопределение операторов SQL для коллекций с использованием аннотаций

@OneToMany
@JoinColumn(name="chaos_fk")
@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")
@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")
private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();

Совет

Порядок параметров важен и определяется порядком Hibernate, обрабатывающим свойства. Вы можете увидеть ожидаемый порядок, включив ведение журнала отладки для уровня org.hibernate.persister.entity. На этом уровне Hibernate будет распечатывать статический SQL, который используется для создания, обновления, удаления и т. д. сущностей. (Чтобы увидеть ожидаемую последовательность не забудьте добавить свой собственный SQL через аннотации или файлы отображения, поскольку это переопределит созданный с помощью Hibernate статический sql)

Переопределение операторов SQL для вторичных таблиц также возможно с помощью @org.hibernate.annotations.Table и любых (или всех) атрибутов sqlInsert, sqlUpdate, sqlDelete:

Примере 18.14. Переопределение операторов SQL для вторичных таблиц

@Entity
@SecondaryTables({
    @SecondaryTable(name = "`Cat nbr1`"),
    @SecondaryTable(name = "Cat2"})
@org.hibernate.annotations.Tables( {
    @Table(appliesTo = "Cat", comment = "My cat table" ),
    @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT,
        sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") )
} )
public class Cat implements Serializable {

В предыдущем примере также показано, что вы можете дать комментарий к данной таблице (первичной или вторичной): этот комментарий будет использоваться для генерации DDL.

Совет

SQL непосредственно выполняется в вашей базе данных, поэтому вы можете использовать любой диалект, который вам нравится. Тем не менее, это уменьшит переносимость вашего отоюражения, если вы используете специфичный SQL базы данных.

И последнее, но не менее важное: хранимые процедуры в большинстве случаев необходимы для возврата количества вставленных, обновленных и удаленных записей. Hibernate всегда регистрирует первый параметр оператора как числовой выходной параметр для операций CUD:

Примере 18.15. Хранимые процедуры и их возвращаемое значение

CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
    RETURN NUMBER IS
BEGIN
    update PERSON
    set
        NAME = uname,
    where
        ID = uid;
    return SQL%ROWCOUNT;
END updatePerson;

18.4. Пользовательский SQL для загрузки

Вы также можете объявить свои собственные запросы SQL (или HQL) для загрузки сущностей. Как и в случае вставок, обновлений и удалений, это можно сделать на уровне отдельных столбцов, как описано в  разделе 5.6. «Преобразователи столбцов: выражения для чтения и записи» или на уровне инструкций. Вот пример переопределения уровня инструкции:

<sql-query name="person">
    <return alias="pers" class="Person" lock-mode="upgrade"/>
    SELECT NAME AS {pers.name}, ID AS {pers.id}
    FROM PERSON
    WHERE xml:id=?
    FOR UPDATE
</sql-query>

Это просто декларация именованного запроса, как обсуждалось ранее. Вы можете ссылаться на этот именованный запрос в отображении классов:

<class name="Person">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name" not-null="true"/>
    <loader query-ref="person"/>
</class>

Это даже работает с хранимыми процедурами.

Вы даже можете определить запрос для загрузки коллекции:

<set name="employments" inverse="true">
    <key/>
    <one-to-many class="Employment"/>
    <loader query-ref="employments"/>
</set>
<sql-query name="employments">
    <load-collection alias="emp" role="Person.employments"/>
    SELECT {emp.*}
    FROM EMPLOYMENT emp
    WHERE EMPLOYER = :id
    ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>

Вы также можете определить загрузчик сущностей, который загружает коллекцию путем выборки с присоединением (join):

<sql-query name="person">
    <return alias="pers" class="Person"/>
    <return-join alias="emp" property="pers.employments"/>
    SELECT NAME AS {pers.*}, {emp.*}
    FROM PERSON pers
    LEFT OUTER JOIN EMPLOYMENT emp
        ON pers.ID = emp.PERSON_ID
    WHERE xml:id=?
</sql-query>

Эквивалент аннотации <loader> представляет собой аннотацию @Loader, как показано в примере 18.11. «Пользовательский CRUD через аннотации».