Skip to content

Commit

Permalink
Merge pull request mybatis#3387 from harawata/oracle-implicit-cursor-…
Browse files Browse the repository at this point in the history
…workaround

oracle implicit cursor workaround
  • Loading branch information
harawata authored Jan 5, 2025
2 parents 7939f2a + f72fe1a commit 6e639f6
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 24 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@
<version>9.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>23.6.0.24.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down Expand Up @@ -254,6 +260,12 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-free</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<!-- For javadoc link -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,34 +247,49 @@ public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException
}

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
ResultSet rs = null;
SQLException e1 = null;

try {
rs = stmt.getResultSet();
} catch (SQLException e) {
// Oracle throws ORA-17283 for implicit cursor
e1 = e;
}

try {
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
} catch (SQLException e) {
throw e1 != null ? e1 : e;
}

return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}

private ResultSetWrapper getNextResultSet(Statement stmt) {
// Making this method tolerant of bad JDBC drivers
try {
if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
// Crazy Standard JDBC way of determining if there are more results
// DO NOT try to 'improve' the condition even if IDE tells you to!
// It's important that getUpdateCount() is called here.
if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
ResultSet rs = stmt.getResultSet();
if (rs == null) {
return getNextResultSet(stmt);
} else {
return new ResultSetWrapper(rs, configuration);
}
// We stopped checking DatabaseMetaData#supportsMultipleResultSets()
// because Oracle driver (incorrectly) returns false

// Crazy Standard JDBC way of determining if there are more results
// DO NOT try to 'improve' the condition even if IDE tells you to!
// It's important that getUpdateCount() is called here.
if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
ResultSet rs = stmt.getResultSet();
if (rs == null) {
return getNextResultSet(stmt);
} else {
return new ResultSetWrapper(rs, configuration);
}
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2024 the original author or authors.
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -92,9 +92,6 @@ void shouldRetainColumnNameCase() throws Exception {
when(rsmd.getColumnLabel(1)).thenReturn("CoLuMn1");
when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
when(stmt.getConnection()).thenReturn(conn);
when(conn.getMetaData()).thenReturn(dbmd);
when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.

final List<Object> results = fastResultSetHandler.handleResultSets(stmt);
assertEquals(1, results.size());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ibatis.submitted.oracle_implicit_cursor;

import java.util.List;
import java.util.Objects;

public class Author {
private Integer id;
private String name;
private List<Book> books;

public Author() {
super();
}

public Author(Integer id, String name, List<Book> books) {
super();
this.id = id;
this.name = name;
this.books = books;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public List<Book> getBooks() {
return books;
}

public void setBooks(List<Book> books) {
this.books = books;
}

@Override
public int hashCode() {
return Objects.hash(books, id, name);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Author)) {
return false;
}
Author other = (Author) obj;
return Objects.equals(books, other.books) && Objects.equals(id, other.id) && Objects.equals(name, other.name);
}

@Override
public String toString() {
return "Author [id=" + id + ", name=" + name + ", books=" + books + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ibatis.submitted.oracle_implicit_cursor;

import java.util.Objects;

public class Book {
private Integer id;
private String name;

public Book() {
super();
}

public Book(Integer id, String name) {
super();
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public int hashCode() {
return Objects.hash(id, name);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Book)) {
return false;
}
Book other = (Book) obj;
return Objects.equals(id, other.id) && Objects.equals(name, other.name);
}

@Override
public String toString() {
return "Book [id=" + id + ", name=" + name + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.submitted.oracle_implicit_cursor;

import java.util.List;

public interface Mapper {

List<Author> selectImplicitCursors_Statement();

List<Author> selectImplicitCursors_Prepared();

List<Author> selectImplicitCursors_Callable();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.submitted.oracle_implicit_cursor;

import static org.junit.jupiter.api.Assertions.assertIterableEquals;

import java.util.List;
import java.util.function.Function;

import org.apache.ibatis.BaseDataTest;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.testcontainers.OracleTestContainer;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("TestcontainersTests")
class OracleImplicitCursorTest {

private static SqlSessionFactory sqlSessionFactory;

@BeforeAll
static void setUp() throws Exception {
Configuration configuration = new Configuration();
Environment environment = new Environment("development", new JdbcTransactionFactory(),
OracleTestContainer.getUnpooledDataSource());
configuration.setEnvironment(environment);
configuration.addMapper(Mapper.class);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/submitted/oracle_implicit_cursor/CreateDB.sql");
}

@Test
void shouldImplicitCursors_Statement() {
doTest(Mapper::selectImplicitCursors_Statement);
}

@Test
void shouldImplicitCursors_Prepared() {
doTest(Mapper::selectImplicitCursors_Prepared);
}

@Test
void shouldImplicitCursors_Callable() {
doTest(Mapper::selectImplicitCursors_Callable);
}

private void doTest(Function<Mapper, List<Author>> query) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
List<Author> authors = query.apply(mapper);
assertIterableEquals(
List.of(new Author(1, "John", List.of(new Book(1, "C#"), new Book(2, "Python"), new Book(5, "Ruby"))),
new Author(2, "Jane", List.of(new Book(3, "SQL"), new Book(4, "Java")))),
authors);
}
}
}
Loading

0 comments on commit 6e639f6

Please sign in to comment.