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.
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;
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
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é.
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?
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.
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)])
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