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