Skip to content

Added selectMapWithList to handle non-unique column as a key #3239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
*/
package org.apache.ibatis.executor.result;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.reflection.MetaObject;
@@ -30,6 +31,7 @@
public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {

private final Map<K, V> mappedResults;
private final Map<K, List<V>> mappedResultsWithList;
private final String mapKey;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
@@ -42,6 +44,7 @@ public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, Objec
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
this.mappedResults = objectFactory.create(Map.class);
this.mappedResultsWithList = objectFactory.create(Map.class);
this.mapKey = mapKey;
}

@@ -52,9 +55,14 @@ public void handleResult(ResultContext<? extends V> context) {
// TODO is that assignment always true?
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
mappedResultsWithList.computeIfAbsent(key, k -> objectFactory.create(List.class)).add(value);
}

public Map<K, V> getMappedResults() {
return mappedResults;
}

public Map<K, List<V>> getMappedResultsWithList() {
return mappedResultsWithList;
}
}
57 changes: 57 additions & 0 deletions src/main/java/org/apache/ibatis/session/SqlSession.java
Original file line number Diff line number Diff line change
@@ -156,6 +156,63 @@ public interface SqlSession extends Closeable {
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

/**
* The selectMapWithList is a special case in that it is designed to convert a list of results into a Map based on one of the
* properties not needed to be unique in the resulting objects. Eg. Return a of Map[Integer,List<Author>] for selectMap("selectAuthors","id")
*
* @param <K>
* the returned Map keys type
* @param <V>
* the returned Map values type
* @param statement
* Unique identifier matching the statement to use.
* @param mapKey
* The property to use as key for each value in the list.
*
* @return Map containing key pair data.
*/
<K, V> Map<K, List<V>> selectMapWithList(String statement, String mapKey);

/**
* The selectMap is a special case in that it is designed to convert a list of results into a Map based on one of the
* properties not needed to be unique in the resulting objects.
*
* @param <K>
* the returned Map keys type
* @param <V>
* the returned Map values type
* @param statement
* Unique identifier matching the statement to use.
* @param parameter
* A parameter object to pass to the statement.
* @param mapKey
* The property to use as key for each value in the list.
*
* @return Map containing key pair data.
*/
<K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey);

/**
* The selectMap is a special case in that it is designed to convert a list of results into a Map based on one of the
* properties not needed to be unique in the resulting objects.
*
* @param <K>
* the returned Map keys type
* @param <V>
* the returned Map values type
* @param statement
* Unique identifier matching the statement to use.
* @param parameter
* A parameter object to pass to the statement.
* @param mapKey
* The property to use as key for each value in the list.
* @param rowBounds
* Bounds to limit object retrieval
*
* @return Map containing key pair data.
*/
<K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey, RowBounds rowBounds);

/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
*
15 changes: 15 additions & 0 deletions src/main/java/org/apache/ibatis/session/SqlSessionManager.java
Original file line number Diff line number Diff line change
@@ -179,6 +179,21 @@ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String map
return sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, String mapKey) {
return sqlSessionProxy.selectMapWithList(statement, mapKey);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey) {
return sqlSessionProxy.selectMapWithList(statement, parameter, mapKey);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return sqlSessionProxy.selectMapWithList(statement, parameter, mapKey, rowBounds);
}

@Override
public <T> Cursor<T> selectCursor(String statement) {
return sqlSessionProxy.selectCursor(statement);
Original file line number Diff line number Diff line change
@@ -96,15 +96,34 @@ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String map

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return (Map<K, V>) executeSelectMap(statement, parameter, mapKey, rowBounds, false);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, String mapKey) {
return this.selectMapWithList(statement, null, mapKey, RowBounds.DEFAULT);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey) {
return this.selectMapWithList(statement, parameter, mapKey, RowBounds.DEFAULT);
}

@Override
public <K, V> Map<K, List<V>> selectMapWithList(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return (Map<K, List<V>>) executeSelectMap(statement, parameter, mapKey, rowBounds, true);
}

private <K, V> Map<K, ?> executeSelectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds, boolean useList) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
return useList ? mapResultHandler.getMappedResultsWithList() : mapResultHandler.getMappedResults();
}

@Override
20 changes: 20 additions & 0 deletions src/test/java/org/apache/ibatis/session/SqlSessionTest.java
Original file line number Diff line number Diff line change
@@ -180,6 +180,26 @@ void shouldSelectAllAuthorsAsMap() {
}
}

@Test
void shouldSelectAllAuthorsAsMapWithList() {
try (SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE)) {
Author author = new Author(103, "jim", "******", "[email protected]", "Something...", null);
session.insert("org.apache.ibatis.domain.blog.mappers.AuthorMapper.insertAuthor", author);

final Map<String, List<Author>> authors = session
.selectMapWithList("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAllAuthors", "username");
authors.forEach((k, v) -> {
if (k.equals("jim")) {
assertEquals(2, v.size());
v.forEach(a -> assertEquals("jim", a.getUsername()));
} else if (k.equals("sally")) {
assertEquals(1, v.size());
v.forEach(a -> assertEquals("sally", a.getUsername()));
}
});
}
}

@Test
void shouldSelectCountOfPosts() {
try (SqlSession session = sqlMapper.openSession()) {