Рубрики
Без рубрики

Лучший способ вызвать хранимую процедуру с помощью JPA и гибернации

Узнайте, как лучше всего вызывать хранимую процедуру базы данных при использовании JPA и спящего режима, чтобы инструкция JDBC была выпущена как можно скорее.

Автор оригинала: Vlad Mihalcea.

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

Я решил написать эту статью, так как способ, которым Hibernate обрабатывает хранимые процедуры, может привести к ORA-01000: превышено максимальное количество открытых курсоров проблемы с Oracle, как описано в этом Тема форума Hibernate или Вопрос о стековом потоке .

Для вызова хранимой процедуры или функции базы данных с помощью JPA можно использовать запрос Хранимая процедура , как показано в следующем примере:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(
    "postId", 
    Long.class, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    "commentCount", 
    Long.class, 
    ParameterMode.OUT
)
.setParameter("postId", 1L);

query.execute();
Long commentCount = (Long) query
.getOutputParameterValue("commentCount");

За кулисами интерфейс Запроса хранимой процедуры расширяется интерфейсом Вызова процедуры для гибернации, поэтому мы можем переписать предыдущий пример следующим образом:

ProcedureCall query = session
.createStoredProcedureCall("count_comments");

query.registerParameter(
    "postId", 
    Long.class, 
    ParameterMode.IN
)
.bindValue(1L);

query.registerParameter(
    "commentCount", 
    Long.class, 
    ParameterMode.OUT
);

Long commentCount = (Long) call
.getOutputs()
.getOutputParameterValue("commentCount");

При вызове метода execute в запросе JPA Хранимой процедуры или outputs().get Current() в режиме гибернации Вызов процедуры Hibernate выполняет следующие действия:

Обратите внимание, что JDBC CallableStatement подготовлено и сохранено в соответствующем Выводе процедуры Impl объекта. При вызове метода getOutputParameterValue Hibernate будет использовать базовое CallableStatement для извлечения параметра OUT .

По этой причине базовый JDBC CallableStatement остается открытым даже после выполнения хранимой процедуры и извлечения параметров OUT или REF_CURSOR .

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

Чтобы проверить это поведение, рассмотрим следующий тестовый случай:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(
    "postId", 
    Long.class, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    "commentCount", 
    Long.class, 
    ParameterMode.OUT
)
.setParameter("postId", 1L);

query.execute();

Long commentCount = (Long) query
.getOutputParameterValue("commentCount");

assertEquals(Long.valueOf(2), commentCount);

ProcedureOutputs procedureOutputs = query
.unwrap(ProcedureOutputs.class);

CallableStatement callableStatement = ReflectionUtils
.getFieldValue(
    procedureOutputs, 
    "callableStatement"
);

assertFalse(callableStatement.isClosed());

procedureOutputs.release();

assertTrue(callableStatement.isClosed());

Обратите внимание, что CallableStatement по-прежнему открыт даже после вызова выполнить или получения количество комментариев ВЫХОД параметр. Только после вызова release на выводах процедуры объект CallableStatement будет закрыт.

Поэтому, чтобы закрыть JDBC CallableStatement как можно скорее, вам следует вызвать release после извлечения всех необходимых данных из хранимой процедуры:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(
    "postId", 
    Long.class, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    "commentCount", 
    Long.class, 
    ParameterMode.OUT
)
.setParameter("postId", 1L);

try {
    query.execute();
    
    Long commentCount = (Long) query
    .getOutputParameterValue("commentCount");

    assertEquals(Long.valueOf(2), commentCount);
} finally {
    query.unwrap(ProcedureOutputs.class).release();
}

CallableStatement callableStatement = ReflectionUtils
.getFieldValue(
    query.unwrap(ProcedureOutputs.class), 
    "callableStatement"
);
assertTrue(callableStatement.isClosed());

Вызов метода release для связанного Вывода процедуры объекта в блоке finally гарантирует, что JDBC CallableStatement будет закрыт независимо от результата вызова хранимой процедуры.

Теперь вызов release вручную немного утомителен, поэтому я решил создать проблему HHH-13215 Jira, которую я интегрировал в ветвь Hibernate ORM 6.

Поэтому начиная с Hibernate 6 и далее вы можете переписать предыдущий пример следующим образом:

Long commentCount = doInJPA(entityManager -> {
    try(ProcedureCall query = entityManager
            .createStoredProcedureQuery("count_comments")
            .unwrap(ProcedureCall.class)) {
             
        return (Long) query
        .registerStoredProcedureParameter(
            "postId",
            Long.class,
            ParameterMode.IN
        )
        .registerStoredProcedureParameter(
            "commentCount",
            Long.class,
            ParameterMode.OUT
        )
        .setParameter("postId", 1L)
        .getOutputParameterValue("commentCount");
    }
});

Намного лучше, правда?

Сделав Вызов процедуры расширение интерфейса Автоклавируемым , мы могли бы использовать инструкцию Java try-with-resource, поэтому вызов хранимой процедуры базы данных был бы менее подробным и более интуитивно понятным, когда дело доходит до освобождения ресурсов JDBC.

Освобождение базового JDBC CallableStatement как можно скорее очень важно при вызове хранимой процедуры с помощью JPA и Hibernate, так как в противном случае курсоры базы данных будут открыты до тех пор, пока текущая транзакция не будет зафиксирована или откатана.

Поэтому, начиная с Hibernate ORM 6, вы должны использовать блок try-finally. Между тем, для гибернации 5 и 4 вы должны использовать блок try-finally, чтобы закрыть CallableStatement сразу после того, как вы закончите получать все необходимые данные.