Skip to content

Commit 571af93

Browse files
committed
Support nested cursor
By specifying a special name NESTED_CURSOR to `resultSet` attribute, Should fix #566
1 parent 6e639f6 commit 571af93

File tree

19 files changed

+829
-83
lines changed

19 files changed

+829
-83
lines changed

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,20 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject
569569
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
570570
}
571571
if (propertyMapping.getResultSet() != null) {
572-
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
573-
return DEFERRED;
572+
if (ResultMapping.NESTED_CURSOR.equals(propertyMapping.getResultSet())) {
573+
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
574+
ResultMap nestedResultMap = resolveDiscriminatedResultMap(rs,
575+
configuration.getResultMap(propertyMapping.getNestedResultMapId()),
576+
getColumnPrefix(columnPrefix, propertyMapping));
577+
ResultSetWrapper rsw = new ResultSetWrapper(rs.getObject(column, ResultSet.class), configuration);
578+
List<Object> results = new ArrayList<>();
579+
handleResultSet(rsw, nestedResultMap, results, null);
580+
linkObjects(metaResultObject, propertyMapping, results.get(0), true);
581+
return metaResultObject.getValue(propertyMapping.getProperty());
582+
} else {
583+
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
584+
return DEFERRED;
585+
}
574586
} else {
575587
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
576588
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
@@ -1527,10 +1539,19 @@ private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws
15271539
}
15281540

15291541
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1542+
linkObjects(metaObject, resultMapping, rowValue, false);
1543+
}
1544+
1545+
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
1546+
boolean isNestedCursorResult) {
15301547
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
15311548
if (collectionProperty != null) {
15321549
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1533-
targetMetaObject.add(rowValue);
1550+
if (isNestedCursorResult) {
1551+
targetMetaObject.addAll((List<?>) rowValue);
1552+
} else {
1553+
targetMetaObject.add(rowValue);
1554+
}
15341555

15351556
// it is possible for pending creations to get set via property mappings,
15361557
// keep track of these, so we can rebuild them.
@@ -1543,10 +1564,16 @@ private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Obj
15431564
pendingPccRelations.put(originalObject, pendingRelation);
15441565
}
15451566
} else {
1546-
metaObject.setValue(resultMapping.getProperty(), rowValue);
1567+
metaObject.setValue(resultMapping.getProperty(),
1568+
isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
15471569
}
15481570
}
15491571

1572+
private Object toSingleObj(List<?> list) {
1573+
// Even if there are multiple elements, silently returns the first one.
1574+
return list.isEmpty() ? null : list.get(0);
1575+
}
1576+
15501577
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
15511578
final String propertyName = resultMapping.getProperty();
15521579
Object propertyValue = metaObject.getValue(propertyName);

src/main/java/org/apache/ibatis/mapping/MappedStatement.java

Lines changed: 7 additions & 1 deletion
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.
@@ -16,6 +16,7 @@
1616
package org.apache.ibatis.mapping;
1717

1818
import java.util.ArrayList;
19+
import java.util.Arrays;
1920
import java.util.Collections;
2021
import java.util.List;
2122

@@ -203,6 +204,11 @@ public MappedStatement build() {
203204
assert mappedStatement.id != null;
204205
assert mappedStatement.sqlSource != null;
205206
assert mappedStatement.lang != null;
207+
if (mappedStatement.resultSets != null
208+
&& Arrays.asList(mappedStatement.resultSets).contains(ResultMapping.NESTED_CURSOR)) {
209+
throw new IllegalStateException(
210+
"Result set name '" + ResultMapping.NESTED_CURSOR + "' is reserved, please assign another name.");
211+
}
206212
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
207213
return mappedStatement;
208214
}

src/main/java/org/apache/ibatis/mapping/ResultMapping.java

Lines changed: 8 additions & 2 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.
@@ -30,6 +30,11 @@
3030
*/
3131
public class ResultMapping {
3232

33+
/**
34+
* Reserved result set name indicating a nested cursor is mapped to this property.
35+
*/
36+
public static final String NESTED_CURSOR = "NESTED_CURSOR";
37+
3338
private Configuration configuration;
3439
private String property;
3540
private String column;
@@ -166,7 +171,8 @@ private void validate() {
166171
if (resultMapping.foreignColumn != null) {
167172
numForeignColumns = resultMapping.foreignColumn.split(",").length;
168173
}
169-
if (numColumns != numForeignColumns) {
174+
if (numColumns != numForeignColumns && !NESTED_CURSOR.equals(resultMapping.resultSet)) {
175+
// Nested cursor does not use 'foreignKey'
170176
throw new IllegalStateException(
171177
"There should be the same number of columns and foreignColumns in property " + resultMapping.property);
172178
}

src/site/es/xdoc/sqlmap-xml.xml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,31 @@ When using this functionality, it is preferable for the entire mapping hierarchy
14281428
columnPrefix="co_" />
14291429
</resultMap>]]></source>
14301430

1431+
1432+
<h4>Nested Cursor for Association</h4>
1433+
1434+
<p>Some databases can return <code>java.sql.ResultSet</code> as a column value.<br />
1435+
Here is the statement and result map.</p>
1436+
1437+
<source><![CDATA[<resultMap id="blogResult" type="Blog">
1438+
<id property="id" column="id" />
1439+
<result property="title" column="title" />
1440+
<association property="author" column="author" resultSet="NESTED_CURSOR">
1441+
<id property="id" column="id" />
1442+
<result property="username" column="username" />
1443+
</association>
1444+
</resultMap>
1445+
1446+
<select id="selectBlog" resultMap="blogResult">
1447+
select b.id, b.name, cursor(
1448+
select a.id, a.username from author a where b.author_id = a.id
1449+
) author from blog b
1450+
</select>]]></source>
1451+
1452+
<p>Compared to the examples in the previous section, the key difference is the <code>resultSet</code> attribute in the <code>&lt;association&gt;</code> element.<br />
1453+
Its value <code>NESTED_CURSOR</code> indicates that the value of the column <code>author</code> is nested cursor.</p>
1454+
1455+
14311456
<h4>ResultSets múltiples en Association</h4>
14321457

14331458
<table>
@@ -1601,6 +1626,29 @@ SELECT * FROM AUTHOR WHERE ID = #{id}]]></source>
16011626
<result property="body" column="body"/>
16021627
</resultMap>]]></source>
16031628

1629+
1630+
<h4>Nested Cursor for Collection</h4>
1631+
1632+
<p>It might be obvious, but nested cursor can return multiple rows.<br />
1633+
Just like <code>&lt;association&gt;</code>, you just need to specify <code>resultSet="NESTED_CURSOR"</code> in the <code>&lt;collection&gt;</code> element.</p>
1634+
1635+
<source><![CDATA[<resultMap id="blogResult" type="Blog">
1636+
<id property="id" column="id" />
1637+
<result property="title" column="title" />
1638+
<collection property="posts" column="posts" resultSet="NESTED_CURSOR">
1639+
<id property="id" column="id" />
1640+
<result property="subject" column="subject" />
1641+
<result property="body" column="body" />
1642+
</collection>
1643+
</resultMap>
1644+
1645+
<select id="selectBlog" resultMap="blogResult">
1646+
select b.id, b.name, cursor(
1647+
select p.id, p.subject, p.body from post p where p.blog_id = b.id
1648+
) posts from blog b
1649+
</select>]]></source>
1650+
1651+
16041652
<h4>ResultSets múltiples en Collection</h4>
16051653

16061654
<p>

src/site/ja/xdoc/sqlmap-xml.xml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,31 @@ User{username=Peter, roles=[Users, Maintainers, Approvers]}
16081608
columnPrefix="co_" />
16091609
</resultMap>]]></source>
16101610

1611+
1612+
<h4>ネストされたカーソルを association にマッピングする</h4>
1613+
1614+
<p>データベースによっては列の値として <code>java.sql.ResultSet</code> 返すことができます。<br />
1615+
このような結果をマッピングする例を説明します。</p>
1616+
1617+
<source><![CDATA[<resultMap id="blogResult" type="Blog">
1618+
<id property="id" column="id" />
1619+
<result property="title" column="title" />
1620+
<association property="author" column="author" resultSet="NESTED_CURSOR">
1621+
<id property="id" column="id" />
1622+
<result property="username" column="username" />
1623+
</association>
1624+
</resultMap>
1625+
1626+
<select id="selectBlog" resultMap="blogResult">
1627+
select b.id, b.name, cursor(
1628+
select a.id, a.username from author a where b.author_id = a.id
1629+
) author from blog b
1630+
</select>]]></source>
1631+
1632+
<p>上の章の例との重要な違いは、<code>&lt;association&gt;</code> 要素の <code>resultSet</code> 属性に特別な値 <code>NESTED_CURSOR</code> を指定している点です。<br />
1633+
これによって、<code>author</code> 列の値をネストされたカーソルとしてマッピングすることができます。</p>
1634+
1635+
16111636
<h4>複数の ResultSet を association にマッピングする</h4>
16121637

16131638
<table>
@@ -1789,6 +1814,29 @@ SELECT * FROM AUTHOR WHERE ID = #{id}]]></source>
17891814
<result property="body" column="body"/>
17901815
</resultMap>]]></source>
17911816

1817+
1818+
<h4>ネストされたカーソルを collection にマッピングする</h4>
1819+
1820+
<p>当然ですが、ネストされたカーソルが複数の値を返す場合もあります。<br />
1821+
先に説明した <code>&lt;association&gt;</code> の場合と同様、 <code>&lt;collection&gt;</code> 要素に <code>resultSet="NESTED_CURSOR"</code> を指定してください。</p>
1822+
1823+
<source><![CDATA[<resultMap id="blogResult" type="Blog">
1824+
<id property="id" column="id" />
1825+
<result property="title" column="title" />
1826+
<collection property="posts" column="posts" resultSet="NESTED_CURSOR">
1827+
<id property="id" column="id" />
1828+
<result property="subject" column="subject" />
1829+
<result property="body" column="body" />
1830+
</collection>
1831+
</resultMap>
1832+
1833+
<select id="selectBlog" resultMap="blogResult">
1834+
select b.id, b.name, cursor(
1835+
select p.id, p.subject, p.body from post p where p.blog_id = b.id
1836+
) posts from blog b
1837+
</select>]]></source>
1838+
1839+
17921840
<h4>複数の ResultSets を collection にマッピングする</h4>
17931841

17941842
<p>

0 commit comments

Comments
 (0)