Skip to content

Commit 52fd6eb

Browse files
committed
Added 'affectData' attribute to SELECT statements
To indicate the SELECT affects DB data. e.g. PostgreSQL's RETURNING, MS SQL Server's OUTPUT
1 parent eccb9c6 commit 52fd6eb

File tree

12 files changed

+112
-11
lines changed

12 files changed

+112
-11
lines changed

src/main/java/org/apache/ibatis/annotations/Select.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
*/
5656
String databaseId() default "";
5757

58+
/**
59+
* Returns whether this select affects DB data.<br>
60+
* e.g. RETURNING of PostgreSQL or OUTPUT of MS SQL Server.
61+
*
62+
* @return {@code true} if this select affects DB data; {@code false} if otherwise
63+
* @since 3.5.12
64+
*/
65+
boolean affectData() default false;
66+
5867
/**
5968
* The container annotation for {@link Select}.
6069
* @author Kazuki Shimizu

src/main/java/org/apache/ibatis/annotations/SelectProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@
9999
*/
100100
String databaseId() default "";
101101

102+
/**
103+
* Returns whether this select affects DB data.<br>
104+
* e.g. RETURNING of PostgreSQL or OUTPUT of MS SQL Server.
105+
*
106+
* @return {@code true} if this select affects DB data; {@code false} if otherwise
107+
* @since 3.5.12
108+
*/
109+
boolean affectData() default false;
110+
102111
/**
103112
* The container annotation for {@link SelectProvider}.
104113
* @author Kazuki Shimizu

src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ public MappedStatement addMappedStatement(
261261
String keyColumn,
262262
String databaseId,
263263
LanguageDriver lang,
264-
String resultSets) {
264+
String resultSets,
265+
boolean dirtySelect) {
265266

266267
if (unresolvedCacheRef) {
267268
throw new IncompleteElementException("Cache-ref not yet resolved");
@@ -285,7 +286,8 @@ public MappedStatement addMappedStatement(
285286
.resultSetType(resultSetType)
286287
.flushCacheRequired(flushCache)
287288
.useCache(useCache)
288-
.cache(currentCache);
289+
.cache(currentCache)
290+
.dirtySelect(dirtySelect);
289291

290292
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
291293
if (statementParameterMap != null) {
@@ -344,12 +346,24 @@ public MappedStatement addMappedStatement(String id, SqlSource sqlSource, Statem
344346
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
345347
String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
346348
boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
347-
LanguageDriver lang) {
349+
LanguageDriver lang, String resultSets) {
348350
return addMappedStatement(
349351
id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
350352
parameterMap, parameterType, resultMap, resultType, resultSetType,
351353
flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
352-
keyColumn, databaseId, lang, null);
354+
keyColumn, databaseId, lang, null, false);
355+
}
356+
357+
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
358+
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
359+
String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
360+
boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
361+
LanguageDriver lang) {
362+
return addMappedStatement(
363+
id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
364+
parameterMap, parameterType, resultMap, resultType, resultSetType,
365+
flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
366+
keyColumn, databaseId, lang, null);
353367
}
354368

355369
private <T> T valueOrDefault(T value, T defaultValue) {

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ void parseStatement(Method method) {
378378
statementAnnotation.getDatabaseId(),
379379
languageDriver,
380380
// ResultSets
381-
options != null ? nullOrEmpty(options.resultSets()) : null);
381+
options != null ? nullOrEmpty(options.resultSets()) : null,
382+
statementAnnotation.isDirtySelect());
382383
});
383384
}
384385

@@ -604,7 +605,7 @@ private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, St
604605

605606
assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
606607
flushCache, useCache, false,
607-
keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null);
608+
keyGenerator, keyProperty, keyColumn, databaseId, languageDriver, null, false);
608609

609610
id = assistant.applyCurrentNamespace(id, false);
610611

@@ -672,13 +673,15 @@ private class AnnotationWrapper {
672673
private final Annotation annotation;
673674
private final String databaseId;
674675
private final SqlCommandType sqlCommandType;
676+
private boolean dirtySelect;
675677

676678
AnnotationWrapper(Annotation annotation) {
677679
super();
678680
this.annotation = annotation;
679681
if (annotation instanceof Select) {
680682
databaseId = ((Select) annotation).databaseId();
681683
sqlCommandType = SqlCommandType.SELECT;
684+
dirtySelect = ((Select) annotation).affectData();
682685
} else if (annotation instanceof Update) {
683686
databaseId = ((Update) annotation).databaseId();
684687
sqlCommandType = SqlCommandType.UPDATE;
@@ -691,6 +694,7 @@ private class AnnotationWrapper {
691694
} else if (annotation instanceof SelectProvider) {
692695
databaseId = ((SelectProvider) annotation).databaseId();
693696
sqlCommandType = SqlCommandType.SELECT;
697+
dirtySelect = ((SelectProvider) annotation).affectData();
694698
} else if (annotation instanceof UpdateProvider) {
695699
databaseId = ((UpdateProvider) annotation).databaseId();
696700
sqlCommandType = SqlCommandType.UPDATE;
@@ -723,5 +727,9 @@ SqlCommandType getSqlCommandType() {
723727
String getDatabaseId() {
724728
return databaseId;
725729
}
730+
731+
boolean isDirtySelect() {
732+
return dirtySelect;
733+
}
726734
}
727735
}

src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,12 @@ public void parseStatementNode() {
109109
String keyProperty = context.getStringAttribute("keyProperty");
110110
String keyColumn = context.getStringAttribute("keyColumn");
111111
String resultSets = context.getStringAttribute("resultSets");
112+
boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
112113

113114
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
114115
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
115116
resultSetTypeEnum, flushCache, useCache, resultOrdered,
116-
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
117+
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
117118
}
118119

119120
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
@@ -160,7 +161,7 @@ private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> paramete
160161
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
161162
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
162163
resultSetTypeEnum, flushCache, useCache, resultOrdered,
163-
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
164+
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null, false);
164165

165166
id = builderAssistant.applyCurrentNamespace(id, false);
166167

src/main/java/org/apache/ibatis/mapping/MappedStatement.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public final class MappedStatement {
5656
private Log statementLog;
5757
private LanguageDriver lang;
5858
private String[] resultSets;
59+
private boolean dirtySelect;
5960

6061
MappedStatement() {
6162
// constructor disabled
@@ -174,6 +175,11 @@ public Builder resultSets(String resultSet) {
174175
return this;
175176
}
176177

178+
public Builder dirtySelect(boolean dirtySelect) {
179+
mappedStatement.dirtySelect = dirtySelect;
180+
return this;
181+
}
182+
177183
/**
178184
* Resul sets.
179185
*
@@ -290,6 +296,10 @@ public String[] getResultSets() {
290296
return resultSets;
291297
}
292298

299+
public boolean isDirtySelect() {
300+
return dirtySelect;
301+
}
302+
293303
/**
294304
* Gets the resul sets.
295305
*

src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowB
148148
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
149149
try {
150150
MappedStatement ms = configuration.getMappedStatement(statement);
151+
dirty |= ms.isDirtySelect();
151152
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
152153
} catch (Exception e) {
153154
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);

src/main/resources/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ databaseId CDATA #IMPLIED
184184
lang CDATA #IMPLIED
185185
resultOrdered (true|false) #IMPLIED
186186
resultSets CDATA #IMPLIED
187+
affectData (true|false) #IMPLIED
187188
>
188189

189190
<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>

src/test/java/org/apache/ibatis/submitted/dirty_select/DirtySelectTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,27 @@ void shouldRollbackIfCalled_Provider() {
101101
}
102102
}
103103

104+
@Test
105+
void shouldNonDirtySelectNotUnsetDirtyFlag() {
106+
Integer id;
107+
try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
108+
Mapper mapper = sqlSession.getMapper(Mapper.class);
109+
// INSERT
110+
User user = new User();
111+
user.setName("Bobby");
112+
mapper.insert(user);
113+
id = user.getId();
114+
assertNotNull(id);
115+
assertEquals("Bobby", user.getName());
116+
// Non-dirty SELECT
117+
mapper.selectById(id);
118+
sqlSession.rollback();
119+
}
120+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
121+
Mapper mapper = sqlSession.getMapper(Mapper.class);
122+
User user = mapper.selectById(id);
123+
assertNull(user);
124+
}
125+
}
126+
104127
}

src/test/java/org/apache/ibatis/submitted/dirty_select/Mapper.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.apache.ibatis.submitted.dirty_select;
1717

18+
import org.apache.ibatis.annotations.Insert;
19+
import org.apache.ibatis.annotations.Options;
1820
import org.apache.ibatis.annotations.Select;
1921
import org.apache.ibatis.annotations.SelectProvider;
2022

@@ -23,14 +25,18 @@ public interface Mapper {
2325
@Select("select * from users where id = #{id}")
2426
User selectById(Integer id);
2527

26-
@Select(value = "insert into users (name) values (#{name}) returning id, name")
28+
@Select(value = "insert into users (name) values (#{name}) returning id, name", affectData = true)
2729
User insertReturn(String name);
2830

2931
User insertReturnXml(String name);
3032

31-
@SelectProvider(type = MyProvider.class, method = "getSql")
33+
@SelectProvider(type = MyProvider.class, method = "getSql", affectData = true)
3234
User insertReturnProvider(String name);
3335

36+
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
37+
@Insert("insert into users (name) values (#{name}) returning id, name")
38+
int insert(User user);
39+
3440
static class MyProvider {
3541
public static String getSql() {
3642
return "insert into users (name) values (#{name}) returning id, name";

0 commit comments

Comments
 (0)