diff --git a/src/main/java/org/apache/ibatis/annotations/MapValue.java b/src/main/java/org/apache/ibatis/annotations/MapValue.java new file mode 100644 index 00000000000..a41717c6646 --- /dev/null +++ b/src/main/java/org/apache/ibatis/annotations/MapValue.java @@ -0,0 +1,27 @@ +/* + * Copyright 2009-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.annotations; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MapValue { + + String value(); + +} diff --git a/src/main/java/org/apache/ibatis/binding/MapperMethod.java b/src/main/java/org/apache/ibatis/binding/MapperMethod.java index c822a4e3ec4..92e3c7729c3 100644 --- a/src/main/java/org/apache/ibatis/binding/MapperMethod.java +++ b/src/main/java/org/apache/ibatis/binding/MapperMethod.java @@ -26,6 +26,7 @@ import org.apache.ibatis.annotations.Flush; import org.apache.ibatis.annotations.MapKey; +import org.apache.ibatis.annotations.MapValue; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; @@ -193,9 +194,9 @@ private Map executeForMap(SqlSession sqlSession, Object[] args) { Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); - result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds); + result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), method.getMapValue(), rowBounds); } else { - result = sqlSession.selectMap(command.getName(), param, method.getMapKey()); + result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), method.getMapValue()); } return result; } @@ -277,6 +278,7 @@ public static class MethodSignature { private final boolean returnsOptional; private final Class returnType; private final String mapKey; + private final String mapValue; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; @@ -295,6 +297,7 @@ public MethodSignature(Configuration configuration, Class mapperInterface, Me this.returnsCursor = Cursor.class.equals(this.returnType); this.returnsOptional = Optional.class.equals(this.returnType); this.mapKey = getMapKey(method); + this.mapValue = getMapValue(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); @@ -371,6 +374,10 @@ public String getMapKey() { return mapKey; } + public String getMapValue() { + return mapValue; + } + private String getMapKey(Method method) { String mapKey = null; if (Map.class.isAssignableFrom(method.getReturnType())) { @@ -381,6 +388,18 @@ private String getMapKey(Method method) { } return mapKey; } + + private String getMapValue(Method method) { + String mapValue = null; + if (Map.class.isAssignableFrom(method.getReturnType())) { + final MapValue annotation = method.getAnnotation(MapValue.class); + if (annotation != null) { + mapValue = annotation.value(); + } + } + return mapValue; + } + } } diff --git a/src/main/java/org/apache/ibatis/executor/result/DefaultMapResultHandler.java b/src/main/java/org/apache/ibatis/executor/result/DefaultMapResultHandler.java index f2ee92bcc02..2f933a2ee0a 100644 --- a/src/main/java/org/apache/ibatis/executor/result/DefaultMapResultHandler.java +++ b/src/main/java/org/apache/ibatis/executor/result/DefaultMapResultHandler.java @@ -31,18 +31,20 @@ public class DefaultMapResultHandler implements ResultHandler { private final Map mappedResults; private final String mapKey; + private final String mapValue; private final ObjectFactory objectFactory; private final ObjectWrapperFactory objectWrapperFactory; private final ReflectorFactory reflectorFactory; @SuppressWarnings("unchecked") - public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, - ReflectorFactory reflectorFactory) { + public DefaultMapResultHandler(String mapKey, String mapValue, ObjectFactory objectFactory, + ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { this.objectFactory = objectFactory; this.objectWrapperFactory = objectWrapperFactory; this.reflectorFactory = reflectorFactory; this.mappedResults = objectFactory.create(Map.class); this.mapKey = mapKey; + this.mapValue = mapValue; } @Override @@ -51,7 +53,7 @@ public void handleResult(ResultContext context) { final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory); // TODO is that assignment always true? final K key = (K) mo.getValue(mapKey); - mappedResults.put(key, value); + mappedResults.put(key, ((V) (mapValue == null ? value : mo.getValue(mapValue)))); } public Map getMappedResults() { diff --git a/src/main/java/org/apache/ibatis/session/SqlSession.java b/src/main/java/org/apache/ibatis/session/SqlSession.java index bb0f85932dc..c8804c9a092 100644 --- a/src/main/java/org/apache/ibatis/session/SqlSession.java +++ b/src/main/java/org/apache/ibatis/session/SqlSession.java @@ -156,6 +156,50 @@ public interface SqlSession extends Closeable { */ Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); + /** + * 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 in the resulting objects. + * + * @param + * the returned Map keys type + * @param + * 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 mapValue + * The property to use as value for each value in the list. + * + * @return Map containing key pair data. + */ + Map selectMap(String statement, Object parameter, String mapKey, String mapValue); + + /** + * 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 in the resulting objects. + * + * @param + * the returned Map keys type + * @param + * 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 mapValue + * The property to use as value for each value in the list. + * @param rowBounds + * Bounds to limit object retrieval + * + * @return Map containing key pair data. + */ + Map selectMap(String statement, Object parameter, String mapKey, String mapValue, RowBounds rowBounds); + /** * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator. * diff --git a/src/main/java/org/apache/ibatis/session/SqlSessionManager.java b/src/main/java/org/apache/ibatis/session/SqlSessionManager.java index 183aa878f9c..f79e6ce578b 100644 --- a/src/main/java/org/apache/ibatis/session/SqlSessionManager.java +++ b/src/main/java/org/apache/ibatis/session/SqlSessionManager.java @@ -179,6 +179,16 @@ public Map selectMap(String statement, Object parameter, String map return sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); } + public Map selectMap(String statement, Object parameter, String mapKey, String mapValue) { + return sqlSessionProxy.selectMap(statement, parameter, mapKey, mapValue); + } + + @Override + public Map selectMap(String statement, Object parameter, String mapKey, String mapValue, + RowBounds rowBounds) { + return sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); + } + @Override public Cursor selectCursor(String statement) { return sqlSessionProxy.selectCursor(statement); diff --git a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java index 62b39becea5..e3575726d2e 100644 --- a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java +++ b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java @@ -96,8 +96,19 @@ public Map selectMap(String statement, Object parameter, String map @Override public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { + return this.selectMap(statement, parameter, mapKey, null, rowBounds); + } + + @Override + public Map selectMap(String statement, Object parameter, String mapKey, String mapValue) { + return this.selectMap(statement, parameter, mapKey, mapValue, RowBounds.DEFAULT); + } + + @Override + public Map selectMap(String statement, Object parameter, String mapKey, String mapValue, + RowBounds rowBounds) { final List list = selectList(statement, parameter, rowBounds); - final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler<>(mapKey, + final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler<>(mapKey, mapValue, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); final DefaultResultContext context = new DefaultResultContext<>(); for (V o : list) { diff --git a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java index 0b72e1d385e..6af36983e30 100644 --- a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java +++ b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java @@ -53,6 +53,7 @@ import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; +import org.apache.ibatis.submitted.mapkey_value.NoticeMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -180,6 +181,18 @@ void shouldSelectAllAuthorsAsMap() { } } + @Test + public void shouldGroupStatusAsMap() { + try (SqlSession sqlSession = sqlMapper.openSession()) { + NoticeMapper mapper = sqlSession.getMapper(NoticeMapper.class); + Map statusCount = mapper.groupStatus(); + assertEquals(2, statusCount.size()); + assertEquals(statusCount.get(1), 3); + assertEquals(statusCount.get(2), 4); + + } + } + @Test void shouldSelectCountOfPosts() { try (SqlSession session = sqlMapper.openSession()) { diff --git a/src/test/java/org/apache/ibatis/submitted/mapkey_value/NoticeMapper.java b/src/test/java/org/apache/ibatis/submitted/mapkey_value/NoticeMapper.java new file mode 100644 index 00000000000..963b195eec4 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/mapkey_value/NoticeMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2009-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.mapkey_value; + +import java.util.Map; + +import org.apache.ibatis.annotations.MapKey; +import org.apache.ibatis.annotations.MapValue; + +public interface NoticeMapper { + + @MapKey("status") + @MapValue("count") + Map groupStatus(); +} diff --git a/src/test/resources/org/apache/ibatis/builder/MapperConfig.xml b/src/test/resources/org/apache/ibatis/builder/MapperConfig.xml index 52de5ab336d..5b3de89050c 100644 --- a/src/test/resources/org/apache/ibatis/builder/MapperConfig.xml +++ b/src/test/resources/org/apache/ibatis/builder/MapperConfig.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-dataload.sql b/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-dataload.sql index 94f92331c32..bddd00eff82 100644 --- a/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-dataload.sql +++ b/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-dataload.sql @@ -1,5 +1,5 @@ -- --- Copyright 2009-2022 the original author or authors. +-- Copyright 2009-2023 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. @@ -49,10 +49,18 @@ INSERT INTO comment (id,post_id,name,comment) VALUES (3,3,'rider','I prefer moto -- 4 5 6 7 INSERT INTO node (id, parent_id) VALUES (1,null); -INSERT INTO node (id, parent_id) VALUES (2,1); +INSERT INTO node (id, parent_id) VALUES (2,1); INSERT INTO node (id, parent_id) VALUES (3,1); INSERT INTO node (id, parent_id) VALUES (4,2); INSERT INTO node (id, parent_id) VALUES (5,2); INSERT INTO node (id, parent_id) VALUES (6,3); INSERT INTO node (id, parent_id) VALUES (7,3); + +INSERT INTO notice (id, status) VALUES (1,1); +INSERT INTO notice (id, status) VALUES (2,2); +INSERT INTO notice (id, status) VALUES (3,1); +INSERT INTO notice (id, status) VALUES (4,2); +INSERT INTO notice (id, status) VALUES (5,1); +INSERT INTO notice (id, status) VALUES (6,2); +INSERT INTO notice (id, status) VALUES (7,2); diff --git a/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-schema.sql b/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-schema.sql index e4304d1ab6c..be6d98e6094 100644 --- a/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-schema.sql +++ b/src/test/resources/org/apache/ibatis/databases/blog/blog-derby-schema.sql @@ -1,5 +1,5 @@ -- --- Copyright 2009-2022 the original author or authors. +-- Copyright 2009-2023 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. @@ -80,6 +80,12 @@ parent_id INT, PRIMARY KEY(id) ); +CREATE TABLE notice ( +id INT NOT NULL, +status INT NOT NULL, +PRIMARY KEY(id) +); + CREATE PROCEDURE selectTwoSetsOfAuthors(DP1 INTEGER, DP2 INTEGER) PARAMETER STYLE JAVA LANGUAGE JAVA