Skip to content

Commit

Permalink
Merge pull request #3388 from harawata/gh/566-oracle-nested-cursor
Browse files Browse the repository at this point in the history
Support nested cursor
  • Loading branch information
harawata authored Jan 26, 2025
2 parents 7d38134 + 03dc3e4 commit 123c42e
Show file tree
Hide file tree
Showing 19 changed files with 973 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap,
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
if (propertyMapping.getNestedResultMapId() != null && !JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
Expand Down Expand Up @@ -568,6 +568,11 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
}
if (JdbcType.CURSOR.equals(propertyMapping.getJdbcType())) {
List<Object> results = getNestedCursorValue(rs, propertyMapping, columnPrefix);
linkObjects(metaResultObject, propertyMapping, results.get(0), true);
return metaResultObject.getValue(propertyMapping.getProperty());
}
if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
Expand All @@ -578,6 +583,18 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject
}
}

private List<Object> getNestedCursorValue(ResultSet rs, ResultMapping propertyMapping, String parentColumnPrefix)
throws SQLException {
final String column = prependPrefix(propertyMapping.getColumn(), parentColumnPrefix);
ResultMap nestedResultMap = resolveDiscriminatedResultMap(rs,
configuration.getResultMap(propertyMapping.getNestedResultMapId()),
getColumnPrefix(parentColumnPrefix, propertyMapping));
ResultSetWrapper rsw = new ResultSetWrapper(rs.getObject(column, ResultSet.class), configuration);
List<Object> results = new ArrayList<>();
handleResultSet(rsw, nestedResultMap, results, null);
return results;
}

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
MetaObject metaObject, String columnPrefix) throws SQLException {
final String mapKey = resultMap.getId() + ":" + columnPrefix;
Expand Down Expand Up @@ -761,6 +778,15 @@ Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType
try {
if (constructorMapping.getNestedQueryId() != null) {
value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
} else if (JdbcType.CURSOR.equals(constructorMapping.getJdbcType())) {
List<?> result = (List<?>) getNestedCursorValue(rsw.getResultSet(), constructorMapping, columnPrefix).get(0);
if (objectFactory.isCollection(parameterType)) {
MetaObject collection = configuration.newMetaObject(objectFactory.create(parameterType));
collection.addAll((List<?>) result);
value = collection.getOriginalObject();
} else {
value = toSingleObj(result);
}
} else if (constructorMapping.getNestedResultMapId() != null) {
final String constructorColumnPrefix = getColumnPrefix(columnPrefix, constructorMapping);
final ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(),
Expand Down Expand Up @@ -1527,10 +1553,19 @@ private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws
}

private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
linkObjects(metaObject, resultMapping, rowValue, false);
}

private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue,
boolean isNestedCursorResult) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
if (isNestedCursorResult) {
targetMetaObject.addAll((List<?>) rowValue);
} else {
targetMetaObject.add(rowValue);
}

// it is possible for pending creations to get set via property mappings,
// keep track of these, so we can rebuild them.
Expand All @@ -1543,10 +1578,16 @@ private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Obj
pendingPccRelations.put(originalObject, pendingRelation);
}
} else {
metaObject.setValue(resultMapping.getProperty(), rowValue);
metaObject.setValue(resultMapping.getProperty(),
isNestedCursorResult ? toSingleObj((List<?>) rowValue) : rowValue);
}
}

private Object toSingleObj(List<?> list) {
// Even if there are multiple elements, silently returns the first one.
return list.isEmpty() ? null : list.get(0);
}

private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
final String propertyName = resultMapping.getProperty();
Object propertyValue = metaObject.getValue(propertyName);
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
6 changes: 3 additions & 3 deletions src/main/java/org/apache/ibatis/mapping/ResultMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Set;

import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

/**
* @author Clinton Begin
Expand Down Expand Up @@ -85,9 +86,8 @@ public ResultMap build() {

for (ResultMapping resultMapping : resultMap.resultMappings) {
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps
|| resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null;

resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || resultMapping.getNestedResultMapId() != null
&& resultMapping.getResultSet() == null && !JdbcType.CURSOR.equals(resultMapping.getJdbcType());
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
Expand Down
48 changes: 48 additions & 0 deletions src/site/es/xdoc/sqlmap-xml.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,31 @@ When using this functionality, it is preferable for the entire mapping hierarchy
columnPrefix="co_" />
</resultMap>]]></source>


<h4>Nested Cursor for Association</h4>

<p>Some databases can return <code>java.sql.ResultSet</code> as a column value.<br />
Here is the statement and result map.</p>

<source><![CDATA[<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title" />
<association property="author" column="author" jdbcType="CURSOR">
<id property="id" column="id" />
<result property="username" column="username" />
</association>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select b.id, b.name, cursor(
select a.id, a.username from author a where b.author_id = a.id
) author from blog b
</select>]]></source>

<p>Compared to the examples in the previous section, the key difference is the <code>jdbcType</code> attribute in the <code>&lt;association&gt;</code> element.<br />
Its value <code>CURSOR</code> indicates that the value of the column <code>author</code> is nested cursor.</p>


<h4>ResultSets múltiples en Association</h4>

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


<h4>Nested Cursor for Collection</h4>

<p>It might be obvious, but nested cursor can return multiple rows.<br />
Just like <code>&lt;association&gt;</code>, you just need to specify <code>jdbcType="CURSOR"</code> in the <code>&lt;collection&gt;</code> element.</p>

<source><![CDATA[<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title" />
<collection property="posts" column="posts" jdbcType="CURSOR">
<id property="id" column="id" />
<result property="subject" column="subject" />
<result property="body" column="body" />
</collection>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select b.id, b.name, cursor(
select p.id, p.subject, p.body from post p where p.blog_id = b.id
) posts from blog b
</select>]]></source>


<h4>ResultSets múltiples en Collection</h4>

<p>
Expand Down
48 changes: 48 additions & 0 deletions src/site/ja/xdoc/sqlmap-xml.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,31 @@ User{username=Peter, roles=[Users, Maintainers, Approvers]}
columnPrefix="co_" />
</resultMap>]]></source>


<h4>ネストされたカーソルを association にマッピングする</h4>

<p>データベースによっては列の値として <code>java.sql.ResultSet</code> 返すことができます。<br />
このような結果をマッピングする例を説明します。</p>

<source><![CDATA[<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title" />
<association property="author" column="author" jdbcType="CURSOR">
<id property="id" column="id" />
<result property="username" column="username" />
</association>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select b.id, b.name, cursor(
select a.id, a.username from author a where b.author_id = a.id
) author from blog b
</select>]]></source>

<p>上の章の例との重要な違いは、<code>&lt;association&gt;</code> 要素の <code>jdbcType</code> 属性に <code>CURSOR</code> を指定している点です。<br />
これによって、<code>author</code> 列の値をネストされたカーソルとしてマッピングすることができます。</p>


<h4>複数の ResultSet を association にマッピングする</h4>

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


<h4>ネストされたカーソルを collection にマッピングする</h4>

<p>当然ですが、ネストされたカーソルが複数の値を返す場合もあります。<br />
先に説明した <code>&lt;association&gt;</code> の場合と同様、 <code>&lt;collection&gt;</code> 要素に <code>jdbcType="CURSOR"</code> を指定してください。</p>

<source><![CDATA[<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title" />
<collection property="posts" column="posts" jdbcType="CURSOR">
<id property="id" column="id" />
<result property="subject" column="subject" />
<result property="body" column="body" />
</collection>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select b.id, b.name, cursor(
select p.id, p.subject, p.body from post p where p.blog_id = b.id
) posts from blog b
</select>]]></source>


<h4>複数の ResultSets を collection にマッピングする</h4>

<p>
Expand Down
Loading

0 comments on commit 123c42e

Please sign in to comment.