Skip to content

Commit 6e639f6

Browse files
authored
Merge pull request #3387 from harawata/oracle-implicit-cursor-workaround
oracle implicit cursor workaround
2 parents 7939f2a + f72fe1a commit 6e639f6

File tree

10 files changed

+482
-24
lines changed

10 files changed

+482
-24
lines changed

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@
204204
<version>9.1.0</version>
205205
<scope>test</scope>
206206
</dependency>
207+
<dependency>
208+
<groupId>com.oracle.database.jdbc</groupId>
209+
<artifactId>ojdbc8</artifactId>
210+
<version>23.6.0.24.10</version>
211+
<scope>test</scope>
212+
</dependency>
207213
<dependency>
208214
<groupId>org.assertj</groupId>
209215
<artifactId>assertj-core</artifactId>
@@ -254,6 +260,12 @@
254260
<version>${testcontainers.version}</version>
255261
<scope>test</scope>
256262
</dependency>
263+
<dependency>
264+
<groupId>org.testcontainers</groupId>
265+
<artifactId>oracle-free</artifactId>
266+
<version>${testcontainers.version}</version>
267+
<scope>test</scope>
268+
</dependency>
257269
<!-- For javadoc link -->
258270
<dependency>
259271
<groupId>com.microsoft.sqlserver</groupId>

src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -247,34 +247,49 @@ public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException
247247
}
248248

249249
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
250-
ResultSet rs = stmt.getResultSet();
251-
while (rs == null) {
252-
// move forward to get the first resultset in case the driver
253-
// doesn't return the resultset as the first result (HSQLDB)
254-
if (stmt.getMoreResults()) {
255-
rs = stmt.getResultSet();
256-
} else if (stmt.getUpdateCount() == -1) {
257-
// no more results. Must be no resultset
258-
break;
250+
ResultSet rs = null;
251+
SQLException e1 = null;
252+
253+
try {
254+
rs = stmt.getResultSet();
255+
} catch (SQLException e) {
256+
// Oracle throws ORA-17283 for implicit cursor
257+
e1 = e;
258+
}
259+
260+
try {
261+
while (rs == null) {
262+
// move forward to get the first resultset in case the driver
263+
// doesn't return the resultset as the first result (HSQLDB)
264+
if (stmt.getMoreResults()) {
265+
rs = stmt.getResultSet();
266+
} else if (stmt.getUpdateCount() == -1) {
267+
// no more results. Must be no resultset
268+
break;
269+
}
259270
}
271+
} catch (SQLException e) {
272+
throw e1 != null ? e1 : e;
260273
}
274+
261275
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
262276
}
263277

264278
private ResultSetWrapper getNextResultSet(Statement stmt) {
265279
// Making this method tolerant of bad JDBC drivers
266280
try {
267-
if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
268-
// Crazy Standard JDBC way of determining if there are more results
269-
// DO NOT try to 'improve' the condition even if IDE tells you to!
270-
// It's important that getUpdateCount() is called here.
271-
if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
272-
ResultSet rs = stmt.getResultSet();
273-
if (rs == null) {
274-
return getNextResultSet(stmt);
275-
} else {
276-
return new ResultSetWrapper(rs, configuration);
277-
}
281+
// We stopped checking DatabaseMetaData#supportsMultipleResultSets()
282+
// because Oracle driver (incorrectly) returns false
283+
284+
// Crazy Standard JDBC way of determining if there are more results
285+
// DO NOT try to 'improve' the condition even if IDE tells you to!
286+
// It's important that getUpdateCount() is called here.
287+
if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
288+
ResultSet rs = stmt.getResultSet();
289+
if (rs == null) {
290+
return getNextResultSet(stmt);
291+
} else {
292+
return new ResultSetWrapper(rs, configuration);
278293
}
279294
}
280295
} catch (Exception e) {

src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -92,9 +92,6 @@ void shouldRetainColumnNameCase() throws Exception {
9292
when(rsmd.getColumnLabel(1)).thenReturn("CoLuMn1");
9393
when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
9494
when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
95-
when(stmt.getConnection()).thenReturn(conn);
96-
when(conn.getMetaData()).thenReturn(dbmd);
97-
when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.
9895

9996
final List<Object> results = fastResultSetHandler.handleResultSets(stmt);
10097
assertEquals(1, results.size());
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2009-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.ibatis.submitted.oracle_implicit_cursor;
18+
19+
import java.util.List;
20+
import java.util.Objects;
21+
22+
public class Author {
23+
private Integer id;
24+
private String name;
25+
private List<Book> books;
26+
27+
public Author() {
28+
super();
29+
}
30+
31+
public Author(Integer id, String name, List<Book> books) {
32+
super();
33+
this.id = id;
34+
this.name = name;
35+
this.books = books;
36+
}
37+
38+
public Integer getId() {
39+
return id;
40+
}
41+
42+
public void setId(Integer id) {
43+
this.id = id;
44+
}
45+
46+
public String getName() {
47+
return name;
48+
}
49+
50+
public void setName(String name) {
51+
this.name = name;
52+
}
53+
54+
public List<Book> getBooks() {
55+
return books;
56+
}
57+
58+
public void setBooks(List<Book> books) {
59+
this.books = books;
60+
}
61+
62+
@Override
63+
public int hashCode() {
64+
return Objects.hash(books, id, name);
65+
}
66+
67+
@Override
68+
public boolean equals(Object obj) {
69+
if (this == obj) {
70+
return true;
71+
}
72+
if (!(obj instanceof Author)) {
73+
return false;
74+
}
75+
Author other = (Author) obj;
76+
return Objects.equals(books, other.books) && Objects.equals(id, other.id) && Objects.equals(name, other.name);
77+
}
78+
79+
@Override
80+
public String toString() {
81+
return "Author [id=" + id + ", name=" + name + ", books=" + books + "]";
82+
}
83+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2009-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.ibatis.submitted.oracle_implicit_cursor;
18+
19+
import java.util.Objects;
20+
21+
public class Book {
22+
private Integer id;
23+
private String name;
24+
25+
public Book() {
26+
super();
27+
}
28+
29+
public Book(Integer id, String name) {
30+
super();
31+
this.id = id;
32+
this.name = name;
33+
}
34+
35+
public Integer getId() {
36+
return id;
37+
}
38+
39+
public void setId(Integer id) {
40+
this.id = id;
41+
}
42+
43+
public String getName() {
44+
return name;
45+
}
46+
47+
public void setName(String name) {
48+
this.name = name;
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(id, name);
54+
}
55+
56+
@Override
57+
public boolean equals(Object obj) {
58+
if (this == obj) {
59+
return true;
60+
}
61+
if (!(obj instanceof Book)) {
62+
return false;
63+
}
64+
Book other = (Book) obj;
65+
return Objects.equals(id, other.id) && Objects.equals(name, other.name);
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return "Book [id=" + id + ", name=" + name + "]";
71+
}
72+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2009-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.submitted.oracle_implicit_cursor;
17+
18+
import java.util.List;
19+
20+
public interface Mapper {
21+
22+
List<Author> selectImplicitCursors_Statement();
23+
24+
List<Author> selectImplicitCursors_Prepared();
25+
26+
List<Author> selectImplicitCursors_Callable();
27+
28+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2009-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.submitted.oracle_implicit_cursor;
17+
18+
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
19+
20+
import java.util.List;
21+
import java.util.function.Function;
22+
23+
import org.apache.ibatis.BaseDataTest;
24+
import org.apache.ibatis.mapping.Environment;
25+
import org.apache.ibatis.session.Configuration;
26+
import org.apache.ibatis.session.SqlSession;
27+
import org.apache.ibatis.session.SqlSessionFactory;
28+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
29+
import org.apache.ibatis.testcontainers.OracleTestContainer;
30+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
31+
import org.junit.jupiter.api.BeforeAll;
32+
import org.junit.jupiter.api.Tag;
33+
import org.junit.jupiter.api.Test;
34+
35+
@Tag("TestcontainersTests")
36+
class OracleImplicitCursorTest {
37+
38+
private static SqlSessionFactory sqlSessionFactory;
39+
40+
@BeforeAll
41+
static void setUp() throws Exception {
42+
Configuration configuration = new Configuration();
43+
Environment environment = new Environment("development", new JdbcTransactionFactory(),
44+
OracleTestContainer.getUnpooledDataSource());
45+
configuration.setEnvironment(environment);
46+
configuration.addMapper(Mapper.class);
47+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
48+
49+
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
50+
"org/apache/ibatis/submitted/oracle_implicit_cursor/CreateDB.sql");
51+
}
52+
53+
@Test
54+
void shouldImplicitCursors_Statement() {
55+
doTest(Mapper::selectImplicitCursors_Statement);
56+
}
57+
58+
@Test
59+
void shouldImplicitCursors_Prepared() {
60+
doTest(Mapper::selectImplicitCursors_Prepared);
61+
}
62+
63+
@Test
64+
void shouldImplicitCursors_Callable() {
65+
doTest(Mapper::selectImplicitCursors_Callable);
66+
}
67+
68+
private void doTest(Function<Mapper, List<Author>> query) {
69+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
70+
Mapper mapper = sqlSession.getMapper(Mapper.class);
71+
List<Author> authors = query.apply(mapper);
72+
assertIterableEquals(
73+
List.of(new Author(1, "John", List.of(new Book(1, "C#"), new Book(2, "Python"), new Book(5, "Ruby"))),
74+
new Author(2, "Jane", List.of(new Book(3, "SQL"), new Book(4, "Java")))),
75+
authors);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)