Java >> Java tutoriál >  >> Tag >> hibernate

Nejlepší způsob volání uložené procedury pomocí JPA a Hibernate

Úvod

V tomto článku se naučíte, jak nejlépe volat uloženou proceduru při použití JPA a Hibernate, aby byly základní zdroje JDBC uvolněny co nejdříve.

Rozhodl jsem se napsat tento článek, protože způsob, jakým Hibernate zpracovává uložené procedury, může vést k ORA-01000: maximum open cursors exceeded problémy na Oracle, jak je vysvětleno v tomto vláknu fóra Hibernate nebo v otázce StackOverflow.

Jak funguje volání uložené procedury s JPA a Hibernate

Pro volání uložené procedury nebo databázové funkce pomocí JPA můžete použít StoredProcedureQuery jak ukazuje následující příklad:

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");

V zákulisí, StoredProcedureQuery rozhraní je rozšířeno o Hibernate specifické ProcedureCall rozhraní, takže předchozí příklad můžeme přepsat takto:

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");

Při volání execute metoda na JPA StoredProcedureQuery nebo outputs().getCurrent() na Hibernate ProcedureCall , Hibernate provede následující akce:

Všimněte si, že JDBC CallableStatement je připraven a uložen v příslušném ProcedureOutputsImpl objekt. Při volání getOutputParameterValue metoda Hibernate použije základní CallableStatement k načtení OUT parametr.

Z tohoto důvodu je základní JDBC CallableStatement zůstane otevřená i po provedení uložené procedury a načtení OUT nebo REF_CURSOR parametry.

Nyní je ve výchozím nastavení CallableStatement je uzavřen po ukončení aktuálně probíhající databázové transakce, a to buď voláním commit nebo rollback .

Doba testování

Chcete-li toto chování ověřit, zvažte následující testovací případ:

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());

Všimněte si, že CallableStatement je stále otevřená i po zavolání execute nebo načtením commentCount OUT parametr. Pouze po zavolání release na ProcedureOutputs objekt, CallableStatement bude zavřeno.

Uzavření příkazu JDBC co nejdříve

Proto zavřít JDBC CallableStatement co nejdříve zavolejte release po načtení všech požadovaných dat z uložené procedury:

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());

Volání release metoda na přidruženém ProcedureOutputs objekt v posledním bloku zajišťuje, že JDBC CallableStatement je uzavřen bez ohledu na výsledek volání uložené procedury.

Nyní volání release ručně je to trochu zdlouhavé, tak jsem se rozhodl vytvořit problém HHH-13215 Jira, který jsem integroval do větve Hibernate ORM 6.

Proto od Hibernate 6 výše můžete předchozí příklad přepsat takto:

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");
    }
});

Mnohem lepší, že?

Vytvořením ProcedureCall rozhraní rozšířit AutoClosable , mohli bychom použít příkaz try-with-resource Java, takže volání uložené procedury databáze by bylo méně podrobné a intuitivnější, pokud jde o dealokaci zdrojů JDBC.

Závěr

Uvolnění základního JDBC CallableStatement co nejdříve je velmi důležité při volání uložené procedury s JPA a Hibernate, protože jinak budou kurzory databáze otevřené, dokud není aktuální transakce potvrzena nebo vrácena zpět.

Proto počínaje Hibernate ORM 6 byste měli použít blok try-finally. Mezitím pro Hibernate 5 a 4 byste měli použít blok try-finally k uzavření CallableStatement ihned poté, co dokončíte načítání všech potřebných dat.


Java Tag