[JAVA] Une erreur s'est produite lors de l'exécution d'une fonction avec CURSOR défini dans le paramètre OUT de MyBatis dans PostgreSQL.

Comme le titre l'indique, dans PostgreSQL, lorsque j'ai défini une fonction avec CURSOR défini dans le paramètre OUT et que je l'ai exécutée à partir de MyBatis, une erreur s'est produite. J'ai essayé de découvrir la cause et comment l'éviter.

environnement

Fonctions PostgreSQL

La définition de table et la définition de fonction ressemblent à ceci.

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;

Essayez de démarrer à partir de Java normalement

Exécutez en utilisant CallableStatement. Le traitement de fermeture est omis.

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

Le résultat de l'exécution ressemble à ce qui suit. Je peux le faire correctement.

1
hogehoge
fugafuga
2
hogege
fugaga

Essayez de l'exécuter sur MyBatis

Créez une classe Test et son wrapper pour mapper les résultats de l'exécution.

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

Ensuite, créez Mapper. Même si vous omettez «@ Result», le mappage automatique fonctionne bien.

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

Essayez de l'exécuter comme suit.

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

Lorsque je l'exécute, j'obtiens cette erreur:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.postgresql.util.PSQLException:La fonction CallableStatement est exécutée et le paramètre de sortie 1 est java.sql.Types=C'était un type de 2012. Mais java.sql.Types=-Le type 10 a été enregistré.
### 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:La fonction CallableStatement est exécutée et le paramètre de sortie 1 est java.sql.Types=C'était un type de 2012. Mais java.sql.Types=-Le type 10 a été enregistré.

Trouvez la cause de l'erreur

Le côté SQL revient avec le type 2012 (java.sql.Types.REF_CURSOR), mais il semble que le type du côté récepteur est -10 et les types ne correspondent pas. Par conséquent, le type spécifié par registerOutParameter dans CallableStatement semble étrange.

MyBatis utilise la méthode registerOutputParameters de CallableStatementHandler pour définir le TYPE_CODE de JdbcType obtenu à partir de ParameterMapping comme type. CallableStatementHandler.java

Ainsi, quand j'ai regardé JdbcType.CURSOR, j'ai trouvé que TYPE_CODE était -10. JdbcType.java

Donc, la cause est ici. Puisque le commentaire dit Oracle, Oracle -10 est-il au lieu de 2012? ?? Cela signifie-t-il que c'est pour Oracle et ne prend pas en charge PostgreSQL?

J'essaierai de le déplacer d'une manière ou d'une autre

Si vous pouvez définir 2012 avec registerOutParameter, cela semble fonctionner, alors essayez de le configurer avec Interceptor. 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 est enregistré en tant que Types.REF_CURSOR pour les paramètres OUT et INOUT spécifiés. En appelant ʻinvocation.proceed () , le traitement de CallableStatementHandler` est exécuté, donc si vous ne l'exécutez pas à l'avance, la valeur que vous avez définie sera écrasée.

Réessayer

Pour enregistrer un intercepteur, utilisez la méthode ʻaddInterceptor de la classe 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()); //★ Ici ★

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

Le résultat de l'exécution ressemble à ce qui suit.

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

Résumé

Je ne pouvais pas dire s'il s'agissait d'un bug ou d'une spécification. Cependant, je sens que je suis devenu un peu plus familier avec le contenu de MyBatis grâce à diverses enquêtes.

Recommended Posts

Une erreur s'est produite lors de l'exécution d'une fonction avec CURSOR défini dans le paramètre OUT de MyBatis dans PostgreSQL.
Résolution d'une erreur survenue lors de la tentative d'utilisation de Spark dans un environnement où Java 8 et Java 11 coexistent
Comment définir une taille de récupération par défaut si jOOQ obtient une erreur OOM lors de la récupération d'un énorme jeu de résultats
Je souhaite afficher un message d'erreur lors de l'inscription dans la base de données
Lors de l'introduction de JOOQ dans Spring Boot, une histoire qui a été traitée parce qu'une erreur s'est produite autour de Liquidbase
Comment stocker des objets dans PostgreSQL au format JSON avec MyBatis (Mapper XML)
Lors de l'utilisation d'une liste en Java, java.awt.List sort et une erreur se produit
NoClassDefFoundError s'est produit lors de la tentative d'appel d'une fonction Android à partir de Unity via jar
Résumé de l'utilisation du jeu de proxy dans IE lors de la connexion avec Java
[JMH] Que faire si le plug-in JMH Gradle indique ÉCHEC: la compilation a échoué avec une exception. Un échec s'est produit lors de l'exécution de me.champeau.gradle.IsolatedRunner [Gradle]
Vérifiez comment définir le délai d'expiration lors de la connexion avec Spring + HikariCP + MySQL et de l'exécution de SQL
Comment gérer l'erreur ERREUR: lors de l'exécution de gem ... (Gem :: FilePermissionError)
Mappage à une classe avec un objet de valeur dans How to My Batis
[Docker] Une histoire sur une erreur dans la composition de docker
Comment configurer un proxy avec authentification dans Feign
[Java] Comment rechercher des valeurs dans un tableau (ou une liste) avec la méthode contains
Que faire si une exception UnsupportedCharsetException se produit dans un JRE léger
[Java] L'ajout d'un élément à la collection provoque une erreur de compilation
Collecte des informations client lorsqu'une erreur se produit dans une application Web
Comment afficher la valeur lorsqu'il y a un tableau dans le tableau
J'ai essayé de traduire le message d'erreur lors de l'exécution d'Eclipse (Java)
L'histoire que Tomcat a souffert d'une erreur de timeout dans Eclipse
Comment résoudre l'erreur inconnue apparue lors de l'utilisation de slf4j en Java
Il doit être identique à l'exemple lors de la connexion à Twitter, mais une erreur se produit jusqu'à la résolution
Ce que je n'aime pas lors de l'utilisation de l'interface d'une fonction avec des arguments par défaut dans Kotlin depuis Java
Comment définir l'image pour dessiner à gauche / à droite du bouton à l'aide de la police d'icône (Iconics)
Une erreur s'est produite dans le cours gratuit de RubyOnRails Udemy, l'a résolue et est allée jusqu'au bout
[Rails] Que faire si vous obtenez une erreur indiquant "Impossible de trouver un moteur d'exécution JavaScript." Lors de l'exécution de la commande rails dans Catalina