[JAVA] Beim Ausführen einer Funktion in PostgreSQL mit dem Parameter OUT von MyBatis auf CURSOR ist ein Fehler aufgetreten.

Wie der Titel schon sagt, ist in PostgreSQL ein Fehler aufgetreten, als ich eine Funktion mit CURSOR im Parameter OUT definiert und von MyBatis ausgeführt habe. Ich habe versucht, die Ursache herauszufinden und sie zu vermeiden.

Umgebung

PostgreSQL-Funktionen

Die Tabellendefinition und Funktionsdefinition sehen folgendermaßen aus.

CREATE TABLE test (
    id serial,
    hoge character(10),
    fuga character(10)
);

INSERT INTO test (hoge, fuga) VALUES('hogehoge', 'fugafuga');
INSERT INTO test (hoge, fuga) VALUES('hogege', 'fugaga');

CREATE FUNCTION testfunc (rc_out OUT refcursor) RETURNS refcursor AS $$
BEGIN
    OPEN rc_out FOR SELECT * FROM test;
END;
$$ LANGUAGE plpgsql;

Versuchen Sie, normal von Java aus zu laufen

Mit "CallableStatement" ausführen. Eine enge Verarbeitung entfällt.

public class App {
    public static void main(String[] args) throws Exception {
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName("org.postgresql.Driver");
        ds.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
        ds.setUsername("postgres");
        ds.setPassword("postgres");

        Connection conn = ds.getConnection();
        conn.setAutoCommit(false);
        CallableStatement cstmt = conn.prepareCall("{call testfunc(?)}");
        cstmt.registerOutParameter(1, Types.REF_CURSOR);
        cstmt.execute();
        
        ResultSet rs = (ResultSet)cstmt.getObject(1);
        while(rs.next()){
            System.out.println(rs.getInt(1));
            System.out.println(rs.getString(2));
            System.out.println(rs.getString(3));
        }
    }
}

Das Ausführungsergebnis sieht wie folgt aus. Ich kann es richtig machen.

1
hogehoge
fugafuga
2
hogege
fugaga

Versuchen Sie es auf MyBatis

Erstellen Sie eine Testklasse und ihren Wrapper, um die Ausführungsergebnisse zuzuordnen.

@Data
public class Test {
    private int id;
    private String hoge;
    private String fuga;
}
@Data
public class TestWrapper {
    private List<Test> test;
}

Erstellen Sie als Nächstes Mapper. Selbst wenn Sie "@ Result" weglassen, funktioniert die automatische Zuordnung gut.

public interface TestMapper {
    @Select(value = "{call testfunc(#{test, mode=OUT, jdbcType=CURSOR, resultMap=testMap})}")
    @Options(statementType = StatementType.CALLABLE)
    @ResultType(Test.class)
    @Results(id = "testMap", value = {
        @Result(id = true, column = "id", property = "id"),
        @Result(column = "hoge", property = "hoge"),
        @Result(column = "fuga", property = "fuga")
    })
    void out(TestWrapper wrapper);
}

Versuchen Sie es wie folgt.

public class App {
    public static void main(String[] args) throws Exception {
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName("org.postgresql.Driver");
        ds.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
        ds.setUsername("postgres");
        ds.setPassword("postgres");

        Environment env = new Environment("postgres", new JdbcTransactionFactory(), ds);
        Configuration config = new Configuration(env);
        config.addMapper(TestMapper.class);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);

        try(SqlSession session = sqlSessionFactory.openSession()){
            TestWrapper wrapper = new TestWrapper();
            session.getMapper(TestMapper.class).out(wrapper);
            System.out.println(wrapper);
        }
    }
}

Wenn ich es ausführe, erhalte ich folgende Fehlermeldung:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.postgresql.util.PSQLException:Die CallableStatement-Funktion wird ausgeführt und Ausgabeparameter 1 ist Java.sql.Types=Es war ein 2012er Typ. Aber Java.sql.Types=-Typ 10 wurde registriert.
### The error may exist in com/example/TestMapper.java (best guess)
### The error may involve com.example.TestMapper.out-Inline
### The error occurred while setting parameters
### SQL: {call testfunc(?)}
### Cause: org.postgresql.util.PSQLException:Die CallableStatement-Funktion wird ausgeführt und Ausgabeparameter 1 ist Java.sql.Types=Es war ein 2012er Typ. Aber Java.sql.Types=-Typ 10 wurde registriert.

Finden Sie die Fehlerursache

Die SQL-Seite gibt den Typ 2012 (java.sql.Types.REF_CURSOR) zurück, aber es scheint, dass der Typ der empfangenden Seite -10 ist und die Typen nicht übereinstimmen. Daher scheint der von registerOutParameter in CallableStatement angegebene Typ seltsam zu sein.

MyBatis verwendet die Methode "registerOutputParameters" von "CallableStatementHandler", um den "TYPE_CODE" von "JdbcType", der aus "ParameterMapping" erhalten wurde, als Typ festzulegen. CallableStatementHandler.java

Als ich mir "JdbcType.CURSOR" ansah, stellte ich fest, dass "TYPE_CODE" -10 war. JdbcType.java

Die Ursache ist also hier. Ist Oracle -10 statt 2012, da der Kommentar Oracle besagt? ?? Bedeutet das, dass es für Oracle ist und PostgreSQL nicht unterstützt?

Ich werde versuchen, es irgendwie zu bewegen

Wenn Sie 2012 mit "registerOutParameter" einstellen können, scheint es zu funktionieren. Versuchen Sie also, es mit Interceptor einzustellen. http://www.mybatis.org/mybatis-3/ja/configuration.html#plugins


@Intercepts({ @Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }) })
public class CursorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object ret = invocation.proceed();
        if(!(invocation.getArgs()[0] instanceof CallableStatement)){
            return ret;
        }

        CallableStatement cstmt = (CallableStatement) invocation.getArgs()[0];
        List<ParameterMapping> parameterMappings = ((StatementHandler) invocation.getTarget()).getBoundSql()
                .getParameterMappings();

        int parameterIndex = 1;
        for (ParameterMapping parameterMapping : parameterMappings) {
            if ((parameterMapping.getMode() == ParameterMode.INOUT || parameterMapping.getMode() == ParameterMode.OUT)
                    && parameterMapping.getJdbcType() == JdbcType.CURSOR) {
                cstmt.registerOutParameter(parameterIndex, Types.REF_CURSOR);
            }
            parameterIndex++;
        }
        return ret;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

JdbcType.CURSOR ist für die angegebenen OUT- und INOUT-Parameter als Types.REF_CURSOR registriert. Durch Aufrufen von "invocation.proceed ()" wird die Verarbeitung von "CallableStatementHandler" ausgeführt. Wenn Sie diese also nicht im Voraus ausführen, wird der von Ihnen festgelegte Wert überschrieben.

Versuchen Sie es nochmal

Verwenden Sie zum Registrieren eines Interceptors die Methode "addInterceptor" der Klasse "Configuration".

public class App {
    public static void main(String[] args) throws Exception {
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName("org.postgresql.Driver");
        ds.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
        ds.setUsername("postgres");
        ds.setPassword("postgres");

        Environment env = new Environment("postgres", new JdbcTransactionFactory(), ds);
        Configuration config = new Configuration(env);
        config.addMapper(TestMapper.class);
        config.addInterceptor(new CursorInterceptor()); //★ Hier ★

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);

        try(SqlSession session = sqlSessionFactory.openSession()){
            TestWrapper wrapper = new TestWrapper();
            session.getMapper(TestMapper.class).out(wrapper);
            System.out.println(wrapper);
        }
    }
}

Das Ausführungsergebnis sieht wie folgt aus.

TestWrapper(test=[Test(id=1, hoge=hogehoge, fuga=fugafuga), Test(id=2, hoge=hogege, fuga=fugaga)])

Zusammenfassung

Ich konnte nicht sagen, ob dies ein Fehler oder eine Spezifikation war. Ich habe jedoch das Gefühl, dass ich durch verschiedene Untersuchungen ein wenig mit dem Inhalt von MyBatis vertraut geworden bin.

Recommended Posts

Beim Ausführen einer Funktion in PostgreSQL mit dem Parameter OUT von MyBatis auf CURSOR ist ein Fehler aufgetreten.
Es wurde ein Fehler behoben, der beim Versuch auftrat, Spark in einer Umgebung zu verwenden, in der Java 8 und Java 11 gleichzeitig vorhanden sind
So legen Sie eine Standardabrufgröße fest, wenn jOOQ beim Abrufen einer großen Ergebnismenge einen OOM-Fehler erhält
Ich möchte bei der Registrierung in der Datenbank eine Fehlermeldung anzeigen
Bei der Einführung von JOOQ in Spring Boot wurde eine Geschichte behandelt, die behandelt wurde, weil bei Liquidbase ein Fehler aufgetreten ist
Speichern von Objekten in PostgreSQL als JSON mit MyBatis (Mapper XML)
Bei Verwendung einer Liste in Java wird java.awt.List ausgegeben und ein Fehler tritt auf
NoClassDefFoundError trat auf, als versucht wurde, eine Android-Funktion von Unity über jar aufzurufen
Zusammenfassung der Verwendung des im IE festgelegten Proxy-Sets bei der Verbindung mit Java
[JMH] Was tun, wenn das JMH-Gradle-Plugin FAILURE: Build mit einer Ausnahme fehlschlägt? Beim Ausführen von me.champeau.gradle.IsolatedRunner [Gradle] ist ein Fehler aufgetreten.
Überprüfen Sie, wie Sie das Zeitlimit festlegen, wenn Sie eine Verbindung zu Spring + HikariCP + MySQL herstellen und SQL ausführen
Umgang mit dem Fehler FEHLER: Während der Ausführung von gem ... (Gem :: FilePermissionError)
Zuordnung zu einer Klasse mit einem Wertobjekt in How to My Batis
[Docker] Eine Geschichte über einen Fehler beim Erstellen von Docker
So richten Sie einen Proxy mit Authentifizierung in Feign ein
[Java] So suchen Sie mit der Methode includes nach Werten in einem Array (oder einer Liste)
Was tun, wenn in einer Lightweight-JRE eine UnsupportedCharsetException auftritt?
[Java] Das Hinzufügen eines Elements zur Sammlung führt zu einem Kompilierungsfehler
Sammeln von Clientinformationen, wenn in einer Webanwendung ein Fehler auftritt
So geben Sie den Wert aus, wenn sich ein Array im Array befindet
Ich habe versucht, die Fehlermeldung beim Ausführen von Eclipse (Java) zu übersetzen.
Die Geschichte, dass Tomcat in Eclipse unter einem Timeout-Fehler litt
So beheben Sie den unbekannten Fehler, der bei der Verwendung von slf4j in Java aufgetreten ist
Es sollte mit dem Beispiel identisch sein, wenn Sie sich bei Twitter anmelden, es tritt jedoch ein Fehler auf, bis die Lösung behoben ist
Was mir bei der Verwendung der Schnittstelle einer Funktion mit Standardargumenten in Kotlin aus Java nicht gefällt
So stellen Sie das Bild mithilfe der Symbolschriftart (Symbole) links / rechts von der Schaltfläche auf zeichnbar ein
Im kostenlosen Kurs von RubyOnRails Udemy ist ein Fehler aufgetreten, der behoben und bis zum Ende durchgearbeitet wurde
[Rails] Was tun, wenn die Fehlermeldung "JavaScript-Laufzeit konnte nicht gefunden werden?" Angezeigt wird, wenn der Befehl "Rails" in Catalina ausgeführt wird