Skip to content

Commit

Permalink
Support nested cursor
Browse files Browse the repository at this point in the history
By specifying a special name NESTED_CURSOR to `resultSet` attribute,

Should fix mybatis#566
  • Loading branch information
harawata committed Jan 5, 2025
1 parent 6e639f6 commit 571af93
Show file tree
Hide file tree
Showing 19 changed files with 829 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,20 @@ private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
}
if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
if (ResultMapping.NESTED_CURSOR.equals(propertyMapping.getResultSet())) {
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
ResultMap nestedResultMap = resolveDiscriminatedResultMap(rs,
configuration.getResultMap(propertyMapping.getNestedResultMapId()),
getColumnPrefix(columnPrefix, propertyMapping));
ResultSetWrapper rsw = new ResultSetWrapper(rs.getObject(column, ResultSet.class), configuration);
List<Object> results = new ArrayList<>();
handleResultSet(rsw, nestedResultMap, results, null);
linkObjects(metaResultObject, propertyMapping, results.get(0), true);
return metaResultObject.getValue(propertyMapping.getProperty());
} else {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
}
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
Expand Down Expand Up @@ -1527,10 +1539,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 +1564,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
8 changes: 7 additions & 1 deletion src/main/java/org/apache/ibatis/mapping/MappedStatement.java
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 All @@ -16,6 +16,7 @@
package org.apache.ibatis.mapping;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

Expand Down Expand Up @@ -203,6 +204,11 @@ public MappedStatement build() {
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
if (mappedStatement.resultSets != null
&& Arrays.asList(mappedStatement.resultSets).contains(ResultMapping.NESTED_CURSOR)) {
throw new IllegalStateException(
"Result set name '" + ResultMapping.NESTED_CURSOR + "' is reserved, please assign another name.");
}
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/org/apache/ibatis/mapping/ResultMapping.java
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 @@ -30,6 +30,11 @@
*/
public class ResultMapping {

/**
* Reserved result set name indicating a nested cursor is mapped to this property.
*/
public static final String NESTED_CURSOR = "NESTED_CURSOR";

private Configuration configuration;
private String property;
private String column;
Expand Down Expand Up @@ -166,7 +171,8 @@ private void validate() {
if (resultMapping.foreignColumn != null) {
numForeignColumns = resultMapping.foreignColumn.split(",").length;
}
if (numColumns != numForeignColumns) {
if (numColumns != numForeignColumns && !NESTED_CURSOR.equals(resultMapping.resultSet)) {
// Nested cursor does not use 'foreignKey'
throw new IllegalStateException(
"There should be the same number of columns and foreignColumns in property " + resultMapping.property);
}
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" resultSet="NESTED_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>resultSet</code> attribute in the <code>&lt;association&gt;</code> element.<br />
Its value <code>NESTED_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>resultSet="NESTED_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" resultSet="NESTED_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" resultSet="NESTED_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>resultSet</code> 属性に特別な値 <code>NESTED_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>resultSet="NESTED_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" resultSet="NESTED_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 571af93

Please sign in to comment.