From eccb9c68db99bfaf9eb8c716b2a247c2a4644a13 Mon Sep 17 00:00:00 2001
From: Iwao AVE!
Date: Sat, 19 Nov 2022 01:01:08 +0900
Subject: [PATCH 1/4] Added failing tests
Some DBs support INSERT, UPDATE or DELETE statement that returns result set.
PostgreSQL has RETURNING
MS SQL Server has OUTPUT
MyBatis can return results by using @Select, @SelectProvider or , however, rollback does not work as expected because SELECT does not mark the session 'dirty'.
---
.../dirty_select/DirtySelectTest.java | 104 ++++++++++++++++++
.../ibatis/submitted/dirty_select/Mapper.java | 39 +++++++
.../ibatis/submitted/dirty_select/User.java | 38 +++++++
.../submitted/dirty_select/CreateDB.sql | 20 ++++
.../ibatis/submitted/dirty_select/Mapper.xml | 12 ++
5 files changed, 213 insertions(+)
create mode 100644 src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
create mode 100644 src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
create mode 100644 src/test/java/org/apache/ibatis/submitted/dirty_select/User.java
create mode 100644 src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
create mode 100644 src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
new file mode 100644
index 00000000000..4fb43ff6d44
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.dirty_select;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.ibatis.BaseDataTest;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.testcontainers.PgContainer;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("TestcontainersTests")
+class DirtySelectTest {
+
+ private static SqlSessionFactory sqlSessionFactory;
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ Configuration configuration = new Configuration();
+ Environment environment = new Environment("development", new JdbcTransactionFactory(),
+ PgContainer.getUnpooledDataSource());
+ configuration.setEnvironment(environment);
+ configuration.addMapper(Mapper.class);
+ sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
+
+ BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
+ "org/apache/ibatis/submitted/dirty_select/CreateDB.sql");
+ }
+
+ @Test
+ void shouldRollbackIfCalled() {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.insertReturn("Jimmy");
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Jimmy", user.getName());
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
+ @Test
+ void shouldRollbackIfCalled_Xml() {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.insertReturnXml("Jimmy");
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Jimmy", user.getName());
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
+ @Test
+ void shouldRollbackIfCalled_Provider() {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.insertReturnProvider("Jimmy");
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Jimmy", user.getName());
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
new file mode 100644
index 00000000000..d02bfe4e680
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dirty_select;
+
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.SelectProvider;
+
+public interface Mapper {
+
+ @Select("select * from users where id = #{id}")
+ User selectById(Integer id);
+
+ @Select(value = "insert into users (name) values (#{name}) returning id, name")
+ User insertReturn(String name);
+
+ User insertReturnXml(String name);
+
+ @SelectProvider(type = MyProvider.class, method = "getSql")
+ User insertReturnProvider(String name);
+
+ static class MyProvider {
+ public static String getSql() {
+ return "insert into users (name) values (#{name}) returning id, name";
+ }
+ }
+}
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/User.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/User.java
new file mode 100644
index 00000000000..eaec6746f64
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/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.dirty_select;
+
+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/dirty_select/CreateDB.sql b/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
new file mode 100644
index 00000000000..1f254e88333
--- /dev/null
+++ b/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
@@ -0,0 +1,20 @@
+--
+-- 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.
+--
+
+create table users (
+ id serial primary key,
+ name varchar(30)
+);
diff --git a/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml b/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
new file mode 100644
index 00000000000..f8b36b580d6
--- /dev/null
+++ b/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ insert into users (name) values (#{name})
+ returning id, name
+
+
+
From 52fd6ebbf8c6cb6591d389f4613727cf278c70ec Mon Sep 17 00:00:00 2001
From: Iwao AVE!
Date: Sat, 19 Nov 2022 10:45:18 +0900
Subject: [PATCH 2/4] Added 'affectData' attribute to SELECT statements
To indicate the SELECT affects DB data.
e.g. PostgreSQL's RETURNING, MS SQL Server's OUTPUT
---
.../org/apache/ibatis/annotations/Select.java | 9 ++++++++
.../ibatis/annotations/SelectProvider.java | 9 ++++++++
.../builder/MapperBuilderAssistant.java | 22 ++++++++++++++----
.../annotation/MapperAnnotationBuilder.java | 12 ++++++++--
.../builder/xml/XMLStatementBuilder.java | 5 ++--
.../ibatis/mapping/MappedStatement.java | 10 ++++++++
.../session/defaults/DefaultSqlSession.java | 1 +
.../ibatis/builder/xml/mybatis-3-mapper.dtd | 1 +
.../dirty_select/DirtySelectTest.java | 23 +++++++++++++++++++
.../ibatis/submitted/dirty_select/Mapper.java | 10 ++++++--
.../submitted/dirty_select/CreateDB.sql | 2 ++
.../ibatis/submitted/dirty_select/Mapper.xml | 19 ++++++++++++++-
12 files changed, 112 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/apache/ibatis/annotations/Select.java b/src/main/java/org/apache/ibatis/annotations/Select.java
index ada5affeafb..3796871090c 100644
--- a/src/main/java/org/apache/ibatis/annotations/Select.java
+++ b/src/main/java/org/apache/ibatis/annotations/Select.java
@@ -55,6 +55,15 @@
*/
String databaseId() default "";
+ /**
+ * Returns whether this select affects DB data.
+ * e.g. RETURNING of PostgreSQL or OUTPUT of MS SQL Server.
+ *
+ * @return {@code true} if this select affects DB data; {@code false} if otherwise
+ * @since 3.5.12
+ */
+ boolean affectData() default false;
+
/**
* The container annotation for {@link Select}.
* @author Kazuki Shimizu
diff --git a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
index f109ebb43de..282374c6459 100644
--- a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
+++ b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
@@ -99,6 +99,15 @@
*/
String databaseId() default "";
+ /**
+ * Returns whether this select affects DB data.
+ * e.g. RETURNING of PostgreSQL or OUTPUT of MS SQL Server.
+ *
+ * @return {@code true} if this select affects DB data; {@code false} if otherwise
+ * @since 3.5.12
+ */
+ boolean affectData() default false;
+
/**
* The container annotation for {@link SelectProvider}.
* @author Kazuki Shimizu
diff --git a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java
index 993031c997d..4c9341a16a9 100644
--- a/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java
+++ b/src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java
@@ -261,7 +261,8 @@ public MappedStatement addMappedStatement(
String keyColumn,
String databaseId,
LanguageDriver lang,
- String resultSets) {
+ String resultSets,
+ boolean dirtySelect) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
@@ -285,7 +286,8 @@ public MappedStatement addMappedStatement(
.resultSetType(resultSetType)
.flushCacheRequired(flushCache)
.useCache(useCache)
- .cache(currentCache);
+ .cache(currentCache)
+ .dirtySelect(dirtySelect);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
@@ -344,12 +346,24 @@ public MappedStatement addMappedStatement(String id, SqlSource sqlSource, Statem
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class> parameterType,
String resultMap, Class> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
- LanguageDriver lang) {
+ LanguageDriver lang, String resultSets) {
return addMappedStatement(
id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
parameterMap, parameterType, resultMap, resultType, resultSetType,
flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
- keyColumn, databaseId, lang, null);
+ keyColumn, databaseId, lang, null, false);
+ }
+
+ public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
+ SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class> parameterType,
+ String resultMap, Class> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
+ boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
+ LanguageDriver lang) {
+ return addMappedStatement(
+ id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
+ parameterMap, parameterType, resultMap, resultType, resultSetType,
+ flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
+ keyColumn, databaseId, lang, null);
}
private T valueOrDefault(T value, T defaultValue) {
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 dbf3019e876..af32cafc2dc 100644
--- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java
+++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java
@@ -378,7 +378,8 @@ void parseStatement(Method method) {
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
- options != null ? nullOrEmpty(options.resultSets()) : null);
+ options != null ? nullOrEmpty(options.resultSets()) : null,
+ statementAnnotation.isDirtySelect());
});
}
@@ -604,7 +605,7 @@ private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, St
assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
flushCache, useCache, false,
- keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null);
+ keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null, false);
id = assistant.applyCurrentNamespace(id, false);
@@ -672,6 +673,7 @@ private class AnnotationWrapper {
private final Annotation annotation;
private final String databaseId;
private final SqlCommandType sqlCommandType;
+ private boolean dirtySelect;
AnnotationWrapper(Annotation annotation) {
super();
@@ -679,6 +681,7 @@ private class AnnotationWrapper {
if (annotation instanceof Select) {
databaseId = ((Select) annotation).databaseId();
sqlCommandType = SqlCommandType.SELECT;
+ dirtySelect = ((Select) annotation).affectData();
} else if (annotation instanceof Update) {
databaseId = ((Update) annotation).databaseId();
sqlCommandType = SqlCommandType.UPDATE;
@@ -691,6 +694,7 @@ private class AnnotationWrapper {
} else if (annotation instanceof SelectProvider) {
databaseId = ((SelectProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.SELECT;
+ dirtySelect = ((SelectProvider) annotation).affectData();
} else if (annotation instanceof UpdateProvider) {
databaseId = ((UpdateProvider) annotation).databaseId();
sqlCommandType = SqlCommandType.UPDATE;
@@ -723,5 +727,9 @@ SqlCommandType getSqlCommandType() {
String getDatabaseId() {
return databaseId;
}
+
+ boolean isDirtySelect() {
+ return dirtySelect;
+ }
}
}
diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java
index cbe8f63af3c..82316bb49c9 100644
--- a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java
+++ b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java
@@ -109,11 +109,12 @@ public void parseStatementNode() {
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
+ boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
- keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
+ keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
private void processSelectKeyNodes(String id, Class> parameterTypeClass, LanguageDriver langDriver) {
@@ -160,7 +161,7 @@ private void parseSelectKeyNode(String id, XNode nodeToHandle, Class> paramete
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
- keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
+ keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null, false);
id = builderAssistant.applyCurrentNamespace(id, false);
diff --git a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
index af3454869c2..c5269af470f 100644
--- a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
+++ b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java
@@ -56,6 +56,7 @@ public final class MappedStatement {
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
+ private boolean dirtySelect;
MappedStatement() {
// constructor disabled
@@ -174,6 +175,11 @@ public Builder resultSets(String resultSet) {
return this;
}
+ public Builder dirtySelect(boolean dirtySelect) {
+ mappedStatement.dirtySelect = dirtySelect;
+ return this;
+ }
+
/**
* Resul sets.
*
@@ -290,6 +296,10 @@ public String[] getResultSets() {
return resultSets;
}
+ public boolean isDirtySelect() {
+ return dirtySelect;
+ }
+
/**
* Gets the resul sets.
*
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 aca3e32770b..18819dc6b4d 100644
--- a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
+++ b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
@@ -148,6 +148,7 @@ public List selectList(String statement, Object parameter, RowBounds rowB
private List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
+ dirty |= ms.isDirtySelect();
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
diff --git a/src/main/resources/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd b/src/main/resources/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd
index 81965360dae..28aff86fa6e 100644
--- a/src/main/resources/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd
+++ b/src/main/resources/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd
@@ -184,6 +184,7 @@ databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED
+affectData (true|false) #IMPLIED
>
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
index 4fb43ff6d44..0e98ccb5ecd 100644
--- a/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
@@ -101,4 +101,27 @@ void shouldRollbackIfCalled_Provider() {
}
}
+ @Test
+ void shouldNonDirtySelectNotUnsetDirtyFlag() {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ // INSERT
+ User user = new User();
+ user.setName("Bobby");
+ mapper.insert(user);
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Bobby", user.getName());
+ // Non-dirty SELECT
+ mapper.selectById(id);
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
index d02bfe4e680..d59d519fe91 100644
--- a/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
@@ -15,6 +15,8 @@
*/
package org.apache.ibatis.submitted.dirty_select;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
@@ -23,14 +25,18 @@ public interface Mapper {
@Select("select * from users where id = #{id}")
User selectById(Integer id);
- @Select(value = "insert into users (name) values (#{name}) returning id, name")
+ @Select(value = "insert into users (name) values (#{name}) returning id, name", affectData = true)
User insertReturn(String name);
User insertReturnXml(String name);
- @SelectProvider(type = MyProvider.class, method = "getSql")
+ @SelectProvider(type = MyProvider.class, method = "getSql", affectData = true)
User insertReturnProvider(String name);
+ @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
+ @Insert("insert into users (name) values (#{name}) returning id, name")
+ int insert(User user);
+
static class MyProvider {
public static String getSql() {
return "insert into users (name) values (#{name}) returning id, name";
diff --git a/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql b/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
index 1f254e88333..fe7d925cce5 100644
--- a/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
+++ b/src/test/resources/org/apache/ibatis/submitted/dirty_select/CreateDB.sql
@@ -14,6 +14,8 @@
-- limitations under the License.
--
+drop table if exists users;
+
create table users (
id serial primary key,
name varchar(30)
diff --git a/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml b/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
index f8b36b580d6..b6b69ebfc99 100644
--- a/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
+++ b/src/test/resources/org/apache/ibatis/submitted/dirty_select/Mapper.xml
@@ -1,9 +1,26 @@
+
-
insert into users (name) values (#{name})
returning id, name
From 76237b0ad9cd4819eaba7fe3aed1d516ac3a2c1b Mon Sep 17 00:00:00 2001
From: Iwao AVE!
Date: Sun, 27 Nov 2022 05:26:20 +0900
Subject: [PATCH 3/4] Documentation
---
src/site/es/xdoc/java-api.xml | 4 ++--
src/site/es/xdoc/sqlmap-xml.xml | 17 +++++++++++++++++
src/site/ja/xdoc/java-api.xml | 4 ++--
src/site/ja/xdoc/sqlmap-xml.xml | 17 +++++++++++++++++
src/site/ko/xdoc/java-api.xml | 4 ++--
src/site/ko/xdoc/sqlmap-xml.xml | 17 +++++++++++++++++
src/site/xdoc/java-api.xml | 4 ++--
src/site/xdoc/sqlmap-xml.xml | 17 +++++++++++++++++
src/site/zh/xdoc/java-api.xml | 4 ++--
src/site/zh/xdoc/sqlmap-xml.xml | 17 +++++++++++++++++
10 files changed, 95 insertions(+), 10 deletions(-)
diff --git a/src/site/es/xdoc/java-api.xml b/src/site/es/xdoc/java-api.xml
index 3bd7d23a74c..cf5836a57a4 100644
--- a/src/site/es/xdoc/java-api.xml
+++ b/src/site/es/xdoc/java-api.xml
@@ -245,13 +245,13 @@ public interface ResultHandler {
There is method for flushing(executing) batch update statements that stored in a JDBC driver class at any timing. This method can be used when you use the ExecutorType.BATCH as ExecutorType.
flushStatements()]]>
- Métodos de control de transacción
+ Métodos de control de transacción
El parámetro ResultContext te da acceso al objeto resultado en sí mismo, un contador del número de objetos creados y un método booleano stop() que te permite indicar a MyBatis que pare la carga de datos.
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
- Por defecto MyBatis no hace un commit a no ser que haya detectado que la base de datos ha sido modificada por una insert, update o delete. Si has realizado cambios sin llamar a estos métodos, entonces puedes pasar true en al método de commit() y rollback() para asegurar que se realiza el commit (ten en cuenta que aun así no puedes forzar el commit() en modo auto-commit o cuando se usa un gestor de transacciones externo). La mayoría de las veces no tendrás que llamar a rollback() dado que MyBatis lo hará por ti en caso de que no hayas llamado a commit(). Sin embargo, si necesitas un control más fino sobre la sesión, donde puede que haya varios commits, tienes esta opción para hacerlo posible.
+ Por defecto MyBatis no hace un commit a no ser que haya detectado que la base de datos ha sido modificada por una insert, update, delete o select con affectData habilitado. Si has realizado cambios sin llamar a estos métodos, entonces puedes pasar true en al método de commit() y rollback() para asegurar que se realiza el commit (ten en cuenta que aun así no puedes forzar el commit() en modo auto-commit o cuando se usa un gestor de transacciones externo). La mayoría de las veces no tendrás que llamar a rollback() dado que MyBatis lo hará por ti en caso de que no hayas llamado a commit(). Sin embargo, si necesitas un control más fino sobre la sesión, donde puede que haya varios commits, tienes esta opción para hacerlo posible.
NOTA MyBatis-Spring y MyBatis-Guice proporcionan gestión de transacción declarativa. Por tanto si estás usando MyBatis con Spring o Guice consulta sus manuales específicos.
Local Cache
diff --git a/src/site/es/xdoc/sqlmap-xml.xml b/src/site/es/xdoc/sqlmap-xml.xml
index f7d81c60b3f..e7c0f6e2f45 100644
--- a/src/site/es/xdoc/sqlmap-xml.xml
+++ b/src/site/es/xdoc/sqlmap-xml.xml
@@ -201,6 +201,11 @@ ps.setInt(1,id);]]>
false.
+
+ affectData
+ Set this to true when writing a INSERT, UPDATE or DELETE statement that returns data so that the transaction is controlled properly. Also see Transaction Control Method . Default: false (since 3.5.12)
+
+
@@ -405,6 +410,18 @@ Por ejemplo, si la columna id de la tabla Author del ejemplo siguiente fuera aut
+
+
+ As an irregular case, some databases allow INSERT, UPDATE or DELETE statement to return result set (e.g. RETURNING clause of PostgreSQL and MariaDB or OUTPUT clause of MS SQL Server). This type of statement must be written as ]]> to map the returned data.
+
+
+
+ insert into Author (username, password, email, bio)
+ values (#{username}, #{password}, #{email}, #{bio})
+ returning id, username, password, email, bio
+ ]]>
+
diff --git a/src/site/ja/xdoc/java-api.xml b/src/site/ja/xdoc/java-api.xml
index 320e3399fbc..e019d3bdfbb 100644
--- a/src/site/ja/xdoc/java-api.xml
+++ b/src/site/ja/xdoc/java-api.xml
@@ -249,13 +249,13 @@ public interface ResultHandler {
バッチ更新用に JDBC ドライバ内に蓄積されたステートメントを任意のタイミングでデータベースへフラッシュ(実行)するメソッドがあります。このメソッドは、 ExecutorType として ExecutorType.BATCH を使用している場合に使用することができます。
flushStatements()]]>
- トランザクションを制御するメソッド
+ トランザクションを制御するメソッド
トランザクションのスコープを制御するメソッドは4つあります。当然ですが、auto-commit を使用する場合や、外部のトランザクションマネージャーを使っている場合、これらのメソッドは効果がありません。しかし、Connection のインスタンスによって管理されている JDBC トランザクションマネージャーを利用している場合は便利なメソッドです。
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
- デフォルトでは、データベースが insert, update, delete メソッドの実行によって変更されない限り MyBatis は commit を実行しません。何らかの理由でこれらのメソッドを使わずにデータを変更した場合は確実にコミットされるように commit メソッドに引数 true を渡してください(ただし、auto-commit モードのセッションや外部のトランザクションマネージャーを使っている場合は true を渡してもコミットされません)。commit が実行されない場合、MyBatis がロールバックを実行するので、通常明示的に rollback() メソッドを呼び出す必要はありません。しかし、一つのセッションの中で複数のコミットやロールバックが必要とされるようなケースでは、rollback() メソッドを使ってより細かい制御を行うことが可能です。
+ デフォルトでは、データベースの変更を伴うメソッド insert, update, delete, affectData を有効化した select が実行されない限り MyBatis は commit を実行しません。何らかの理由でこれらのメソッドを使わずにデータを変更した場合は確実にコミットされるように commit メソッドに引数 true を渡してください(ただし、auto-commit モードのセッションや外部のトランザクションマネージャーを使っている場合は true を渡してもコミットされません)。commit が実行されない場合、MyBatis がロールバックを実行するので、通常明示的に rollback() メソッドを呼び出す必要はありません。しかし、一つのセッションの中で複数のコミットやロールバックが必要とされるようなケースでは、rollback() メソッドを使ってより細かい制御を行うことが可能です。
NOTE Mybatis-Spring と MyBatis-Guice では宣言的トランザクションがサポートされています。詳細は各サブプロジェクトのドキュメントを参照してください。
ローカルキャッシュ
diff --git a/src/site/ja/xdoc/sqlmap-xml.xml b/src/site/ja/xdoc/sqlmap-xml.xml
index 27b7b92f571..eae6ca58207 100644
--- a/src/site/ja/xdoc/sqlmap-xml.xml
+++ b/src/site/ja/xdoc/sqlmap-xml.xml
@@ -233,6 +233,11 @@ ps.setInt(1,id);]]>
複数の ResultSet を利用する場合にのみ有効です。ステートメントが返す ResultSet にそれぞれ任意の名前を付けてリストアップします。名前はカンマで区切ります。
+
+ affectData
+ ResultSet を返す INSERT, UPDATE, DELETE 文を記述する場合に true をセットします。これによりトランザクション制御が正しく実行されるようになります。トランザクションを制御するメソッド も参照してください。 デフォルト: false (3.5.12 以降)
+
+
@@ -453,6 +458,18 @@ ps.setInt(1,id);]]>
+
+
+ 例外として、INSERT, UPDATE, DELETE 文から ResultSet を返す SQL 文(PostgreSQL, MariaDB の RETURNING , MS SQL Server の OUTPUT など)で結果をマップするためには ]]> を使用する必要があります。
+
+
+
+ insert into Author (username, password, email, bio)
+ values (#{username}, #{password}, #{email}, #{bio})
+ returning id, username, password, email, bio
+]]>
+
diff --git a/src/site/ko/xdoc/java-api.xml b/src/site/ko/xdoc/java-api.xml
index 8d3a6bbb9b5..21028e14b34 100644
--- a/src/site/ko/xdoc/java-api.xml
+++ b/src/site/ko/xdoc/java-api.xml
@@ -316,7 +316,7 @@ public interface ResultHandler {
이 방법은 ExecutorType을 ExecutorType.BATCH로 설정한 경우 사용가능하다.
flushStatements()]]>
- 트랙잭션 제어 메소드
+ 트랙잭션 제어 메소드
트랜잭션을 제어하기 위해 4개의 메소드가 있다.
물론 자동커밋을 선택하였거나 외부 트랜잭션 관리자를 사용하면 영향이 없다.
어쨌든 Connection인스턴스에 의해 관리되고 JDBC 트랜잭션 관리자를 사용하면 이 4개의 메소드를 사용할 수 있다.
@@ -324,7 +324,7 @@ public interface ResultHandler {
void commit(boolean force)
void rollback()
void rollback(boolean force)
- 기본적으로 마이바티스는 insert, update 또는 delete 를 호출하여 데이터베이스가 변경된 것으로 감지하지 않는 한 실제로 커밋하지 않는다.
+
기본적으로 마이바티스는 insert, update, delete 또는 affectData가 활성화된select 를 호출하여 데이터베이스가 변경된 것으로 감지하지 않는 한 실제로 커밋하지 않는다.
이러한 메소드 호출없이 변경되면 커밋된 것으로 보장하기 위해 commit 와 rollback 메소드에 true 값을 전달한다.
참고 MyBatis-Spring 과 MyBatis-Guice는 선언적인 트랜잭션 관리기법을 제공한다.
그래서 스프링이나 쥬스와 함께 마이바티스를 사용한다면 해당되는 메뉴얼을 꼭 참고하길 바란다.
diff --git a/src/site/ko/xdoc/sqlmap-xml.xml b/src/site/ko/xdoc/sqlmap-xml.xml
index 70a6615a425..4ddde0df518 100644
--- a/src/site/ko/xdoc/sqlmap-xml.xml
+++ b/src/site/ko/xdoc/sqlmap-xml.xml
@@ -188,6 +188,11 @@ ps.setInt(1,id);]]>
디폴트값은 false 이다.
+
+ affectData
+ Set this to true when writing a INSERT, UPDATE or DELETE statement that returns data so that the transaction is controlled properly. Also see Transaction Control Method . Default: false (since 3.5.12)
+
+
@@ -392,6 +397,18 @@ ps.setInt(1,id);]]>
+
+
+ As an irregular case, some databases allow INSERT, UPDATE or DELETE statement to return result set (e.g. RETURNING clause of PostgreSQL and MariaDB or OUTPUT clause of MS SQL Server). This type of statement must be written as ]]> to map the returned data.
+
+
+
+ insert into Author (username, password, email, bio)
+ values (#{username}, #{password}, #{email}, #{bio})
+ returning id, username, password, email, bio
+]]>
+
diff --git a/src/site/xdoc/java-api.xml b/src/site/xdoc/java-api.xml
index 8305480e587..f67e1fe959c 100644
--- a/src/site/xdoc/java-api.xml
+++ b/src/site/xdoc/java-api.xml
@@ -251,13 +251,13 @@ public interface ResultHandler {
There is method for flushing (executing) batch update statements that are stored in a JDBC driver class at any time. This method can be used when the ExecutorType is ExecutorType.BATCH.
flushStatements()]]>
- Transaction Control Methods
+ Transaction Control Methods
There are four methods for controlling the scope of a transaction. Of course, these have no effect if you've chosen to use auto-commit or if you're using an external transaction manager. However, if you're using the JDBC transaction manager, managed by the Connection instance, then the four methods that will come in handy are:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
- By default MyBatis does not actually commit unless it detects that the database has been changed by a call to insert, update or delete. If you've somehow made changes without calling these methods, then you can pass true into the commit and rollback methods to guarantee that they will be committed (note, you still can't force a session in auto-commit mode, or one that is using an external transaction manager). Most of the time you won't have to call rollback(), as MyBatis will do that for you if you don't call commit. However, if you need more fine-grained control over a session where multiple commits and rollbacks are possible, you have the rollback option there to make that possible.
+ By default MyBatis does not actually commit unless it detects that the database has been changed by a call to insert, update, delete or select with affectData enabled. If you've somehow made changes without calling these methods, then you can pass true into the commit and rollback methods to guarantee that they will be committed (note, you still can't force a session in auto-commit mode, or one that is using an external transaction manager). Most of the time you won't have to call rollback(), as MyBatis will do that for you if you don't call commit. However, if you need more fine-grained control over a session where multiple commits and rollbacks are possible, you have the rollback option there to make that possible.
NOTE MyBatis-Spring and MyBatis-Guice provide declarative transaction handling. So if you are using MyBatis with Spring or Guice please refer to their specific manuals.
Local Cache
diff --git a/src/site/xdoc/sqlmap-xml.xml b/src/site/xdoc/sqlmap-xml.xml
index 7ba060720c4..21332e04a5a 100644
--- a/src/site/xdoc/sqlmap-xml.xml
+++ b/src/site/xdoc/sqlmap-xml.xml
@@ -245,6 +245,11 @@ ps.setInt(1,id);]]>
be returned by the statement and gives a name to each one. Names are separated by commas.
+
+ affectData
+ Set this to true when writing a INSERT, UPDATE or DELETE statement that returns data so that the transaction is controlled properly. Also see Transaction Control Method . Default: false (since 3.5.12)
+
+
@@ -491,6 +496,18 @@ ps.setInt(1,id);]]>
+
+
+ As an irregular case, some databases allow INSERT, UPDATE or DELETE statement to return result set (e.g. RETURNING clause of PostgreSQL and MariaDB or OUTPUT clause of MS SQL Server). This type of statement must be written as ]]> to map the returned data.
+
+
+
+ insert into Author (username, password, email, bio)
+ values (#{username}, #{password}, #{email}, #{bio})
+ returning id, username, password, email, bio
+]]>
+
diff --git a/src/site/zh/xdoc/java-api.xml b/src/site/zh/xdoc/java-api.xml
index 59a812531fa..e6c8a1f872f 100644
--- a/src/site/zh/xdoc/java-api.xml
+++ b/src/site/zh/xdoc/java-api.xml
@@ -245,7 +245,7 @@ public interface ResultHandler {
当你将 ExecutorType 设置为 ExecutorType.BATCH 时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句。
flushStatements()]]>
- 事务控制方法
+ 事务控制方法
有四个方法用来控制事务作用域。当然,如果你已经设置了自动提交或你使用了外部事务管理器,这些方法就没什么作用了。然而,如果你正在使用由 Connection 实例控制的 JDBC 事务管理器,那么这四个方法就会派上用场:
@@ -253,7 +253,7 @@ public interface ResultHandler {
void commit(boolean force)
void rollback()
void rollback(boolean force)
- 默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新或删除方法改变了数据库。如果你没有使用这些方法提交修改,那么你可以在 commit 和 rollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。
+ 默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新、删除或 select with affectData enabled 方法改变了数据库。如果你没有使用这些方法提交修改,那么你可以在 commit 和 rollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。
提示 MyBatis-Spring 和 MyBatis-Guice 提供了声明式事务处理,所以如果你在使用 Mybatis 的同时使用了 Spring 或者 Guice,请参考它们的手册以获取更多的内容。
本地缓存
diff --git a/src/site/zh/xdoc/sqlmap-xml.xml b/src/site/zh/xdoc/sqlmap-xml.xml
index 00150259943..87b50547083 100644
--- a/src/site/zh/xdoc/sqlmap-xml.xml
+++ b/src/site/zh/xdoc/sqlmap-xml.xml
@@ -233,6 +233,11 @@ ps.setInt(1,id);]]>
这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
+
+ affectData
+ Set this to true when writing a INSERT, UPDATE or DELETE statement that returns data so that the transaction is controlled properly. Also see Transaction Control Method . Default: false (since 3.5.12)
+
+
@@ -461,6 +466,18 @@ ps.setInt(1,id);]]>
+
+
+ As an irregular case, some databases allow INSERT, UPDATE or DELETE statement to return result set (e.g. RETURNING clause of PostgreSQL and MariaDB or OUTPUT clause of MS SQL Server). This type of statement must be written as ]]> to map the returned data.
+
+
+
+ insert into Author (username, password, email, bio)
+ values (#{username}, #{password}, #{email}, #{bio})
+ returning id, username, password, email, bio
+]]>
+
From 9e786dc270325ec95c4ee3a04c1833adfa0e86aa Mon Sep 17 00:00:00 2001
From: Iwao AVE!
Date: Mon, 28 Nov 2022 01:17:37 +0900
Subject: [PATCH 4/4] selectCursor() should respect affectData as well
---
.../session/defaults/DefaultSqlSession.java | 1 +
.../dirty_select/DirtySelectTest.java | 50 +++++++++++++++++++
.../ibatis/submitted/dirty_select/Mapper.java | 7 +++
3 files changed, 58 insertions(+)
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 18819dc6b4d..2dad6548e3c 100644
--- a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
+++ b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
@@ -120,6 +120,7 @@ public Cursor selectCursor(String statement, Object parameter) {
public Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
+ dirty |= ms.isDirtySelect();
Cursor cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
index 0e98ccb5ecd..64d6cd31a50 100644
--- a/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java
@@ -17,7 +17,10 @@
import static org.junit.jupiter.api.Assertions.*;
+import java.util.Iterator;
+
import org.apache.ibatis.BaseDataTest;
+import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
@@ -101,6 +104,27 @@ void shouldRollbackIfCalled_Provider() {
}
}
+ @Test
+ void shouldRollbackIfCalled_Cursor() throws Exception {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ try (Cursor cursor = mapper.insertReturnCursor("Kate")) {
+ Iterator iterator = cursor.iterator();
+ User user = iterator.next();
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Kate", user.getName());
+ }
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
@Test
void shouldNonDirtySelectNotUnsetDirtyFlag() {
Integer id;
@@ -124,4 +148,30 @@ void shouldNonDirtySelectNotUnsetDirtyFlag() {
}
}
+ @Test
+ void shouldNonDirtySelectNotUnsetDirtyFlag_Cursor() throws Exception {
+ Integer id;
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ // INSERT
+ User user = new User();
+ user.setName("Bobby");
+ mapper.insert(user);
+ id = user.getId();
+ assertNotNull(id);
+ assertEquals("Bobby", user.getName());
+ // Non-dirty SELECT
+ try (Cursor cursor = mapper.selectCursorById(id)) {
+ Iterator iterator = cursor.iterator();
+ iterator.next();
+ }
+ sqlSession.rollback();
+ }
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ Mapper mapper = sqlSession.getMapper(Mapper.class);
+ User user = mapper.selectById(id);
+ assertNull(user);
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
index d59d519fe91..65fc52fe40f 100644
--- a/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
+++ b/src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java
@@ -19,15 +19,22 @@
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
+import org.apache.ibatis.cursor.Cursor;
public interface Mapper {
@Select("select * from users where id = #{id}")
User selectById(Integer id);
+ @Select("select * from users where id = #{id}")
+ Cursor selectCursorById(Integer id);
+
@Select(value = "insert into users (name) values (#{name}) returning id, name", affectData = true)
User insertReturn(String name);
+ @Select(value = "insert into users (name) values (#{name}) returning id, name", affectData = true)
+ Cursor insertReturnCursor(String name);
+
User insertReturnXml(String name);
@SelectProvider(type = MyProvider.class, method = "getSql", affectData = true)