diff --git a/src/main/java/org/apache/ibatis/binding/MapperMethod.java b/src/main/java/org/apache/ibatis/binding/MapperMethod.java index 6990b4f4f63..69097a50124 100644 --- a/src/main/java/org/apache/ibatis/binding/MapperMethod.java +++ b/src/main/java/org/apache/ibatis/binding/MapperMethod.java @@ -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. @@ -23,6 +23,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.apache.ibatis.annotations.Flush; import org.apache.ibatis.annotations.MapKey; @@ -43,6 +47,7 @@ * @author Eduardo Macarron * @author Lasse Voss * @author Kazuki Shimizu + * @author Yanming Zhou */ public class MapperMethod { @@ -82,6 +87,11 @@ public Object execute(SqlSession sqlSession, Object[] args) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); + } else if (method.returnsStream()) { + Cursor cursor = executeForCursor(sqlSession, args); + result = StreamSupport + .stream(Spliterators.spliteratorUnknownSize(cursor.iterator(), Spliterator.ORDERED), false) + .onClose(cursor::close); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); @@ -274,6 +284,7 @@ public static class MethodSignature { private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; + private final boolean returnsStream; private final boolean returnsOptional; private final Class returnType; private final String mapKey; @@ -293,6 +304,7 @@ public MethodSignature(Configuration configuration, Class mapperInterface, Me this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); + this.returnsStream = Stream.class.equals(this.returnType); this.returnsOptional = Optional.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; @@ -341,6 +353,10 @@ public boolean returnsCursor() { return returnsCursor; } + public boolean returnsStream() { + return returnsStream; + } + /** * return whether return type is {@code java.util.Optional}. * diff --git a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java index a0d802f3d19..2ea205b3955 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -93,6 +93,7 @@ /** * @author Clinton Begin * @author Kazuki Shimizu + * @author Yanming Zhou */ public class MapperAnnotationBuilder { @@ -401,7 +402,8 @@ private static Class getReturnType(Method method, Class type) { } else if (resolvedReturnType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType; Class rawType = (Class) parameterizedType.getRawType(); - if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) { + if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType) + || Stream.class.isAssignableFrom(rawType)) { Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); if (actualTypeArguments != null && actualTypeArguments.length == 1) { Type returnTypeParameter = actualTypeArguments[0]; diff --git a/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.java b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.java new file mode 100644 index 00000000000..43bec4d8033 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2009-2022 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.stream_on_mapper_method; + +import java.util.stream.Stream; + +import org.apache.ibatis.annotations.Select; + +public interface Mapper { + + @Select("select * from users") + Stream findAll(); + + Stream findAllUsingXml(); + +} diff --git a/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/StreamOnMapperMethodTest.java b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/StreamOnMapperMethodTest.java new file mode 100644 index 00000000000..6db98f05cc1 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/StreamOnMapperMethodTest.java @@ -0,0 +1,73 @@ +/* + * 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. + * 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.stream_on_mapper_method; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.Reader; +import java.util.stream.Stream; + +import org.apache.ibatis.BaseDataTest; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Tests for support the {@code java.util.stream.Stream} as return type of mapper method. + * + * @author Yanming Zhou + */ +class StreamOnMapperMethodTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setUp() throws Exception { + // create an SqlSessionFactory + try (Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/stream_on_mapper_method/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + } + + // populate in-memory database + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), + "org/apache/ibatis/submitted/stream_on_mapper_method/CreateDB.sql"); + } + + @Test + void returnStream() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + try (Stream stream = mapper.findAll()) { + assertEquals(2, stream.count()); + } + } + } + + @Test + void returnStreamUsingXml() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + try (Stream stream = mapper.findAllUsingXml()) { + assertEquals(2, stream.count()); + } + } + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/User.java b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/User.java new file mode 100644 index 00000000000..e1268de3041 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/stream_on_mapper_method/User.java @@ -0,0 +1,38 @@ +/* + * Copyright 2009-2022 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.stream_on_mapper_method; + +public class User { + + private Integer id; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/CreateDB.sql b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/CreateDB.sql new file mode 100644 index 00000000000..204a48a3908 --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/CreateDB.sql @@ -0,0 +1,25 @@ +-- +-- Copyright 2009-2022 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. +-- + +drop table users if exists; + +create table users ( + id int, + name varchar(20) +); + +insert into users (id, name) values +(1, 'User1'), (2, 'User2'); diff --git a/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.xml b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.xml new file mode 100644 index 00000000000..b9e6382f5f2 --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/Mapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/mybatis-config.xml b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/mybatis-config.xml new file mode 100644 index 00000000000..b7d30ed0410 --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/stream_on_mapper_method/mybatis-config.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + +