Skip to content

Commit 0b59460

Browse files
authored
Merge pull request #2741 from harawata/dirty-select
Add `affectData` attribute to `@Select`, `@SelectProvider` and `<select />`
2 parents 92ea0e7 + 9e786dc commit 0b59460

File tree

23 files changed

+475
-18
lines changed

23 files changed

+475
-18
lines changed

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

+9
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

+9
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

+18-4
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

+10-2
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

+3-2
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

+10
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

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public <T> Cursor<T> selectCursor(String statement, Object parameter) {
120120
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
121121
try {
122122
MappedStatement ms = configuration.getMappedStatement(statement);
123+
dirty |= ms.isDirtySelect();
123124
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
124125
registerCursor(cursor);
125126
return cursor;
@@ -148,6 +149,7 @@ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowB
148149
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
149150
try {
150151
MappedStatement ms = configuration.getMappedStatement(statement);
152+
dirty |= ms.isDirtySelect();
151153
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
152154
} catch (Exception e) {
153155
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);

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

+1
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/site/es/xdoc/java-api.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ public interface ResultHandler<T> {
245245
<p>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 <code>ExecutorType.BATCH</code> as <code>ExecutorType</code>.</p>
246246
<source><![CDATA[List<BatchResult> flushStatements()]]></source>
247247

248-
<h5>Métodos de control de transacción</h5>
248+
<h5 id="transaction-control-methods">Métodos de control de transacción</h5>
249249
<p>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.</p>
250250
<source>void commit()
251251
void commit(boolean force)
252252
void rollback()
253253
void rollback(boolean force)</source>
254-
<p>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.</p>
254+
<p>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 <code>affectData</code> 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.</p>
255255
<p><span class="label important">NOTA</span> 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.</p>
256256

257257
<h5>Local Cache</h5>

src/site/es/xdoc/sqlmap-xml.xml

+17
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ ps.setInt(1,id);]]></source>
201201
<code>false</code>.
202202
</td>
203203
</tr>
204+
<tr>
205+
<td><code>affectData</code></td>
206+
<td>Set this to true when writing a INSERT, UPDATE or DELETE statement that returns data so that the transaction is controlled properly. Also see <a href="./java-api.html#transaction-control-methods">Transaction Control Method</a>. Default: <code>false</code> (since 3.5.12)
207+
</td>
208+
</tr>
204209
</tbody>
205210
</table>
206211
</subsection>
@@ -405,6 +410,18 @@ Por ejemplo, si la columna id de la tabla Author del ejemplo siguiente fuera aut
405410
</tr>
406411
</tbody>
407412
</table>
413+
414+
<p>
415+
As an irregular case, some databases allow INSERT, UPDATE or DELETE statement to return result set (e.g. <code>RETURNING</code> clause of PostgreSQL and MariaDB or <code>OUTPUT</code> clause of MS SQL Server). This type of statement must be written as <code><![CDATA[<select>]]></code> to map the returned data.
416+
</p>
417+
418+
<source><![CDATA[<select id="insertAndGetAuthor" resultType="domain.blog.Author"
419+
affectData="true" flushCache="true">
420+
insert into Author (username, password, email, bio)
421+
values (#{username}, #{password}, #{email}, #{bio})
422+
returning id, username, password, email, bio
423+
</select>]]></source>
424+
408425
</subsection>
409426

410427
<subsection name="sql">

src/site/ja/xdoc/java-api.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,13 @@ public interface ResultHandler<T> {
249249
<p>バッチ更新用に JDBC ドライバ内に蓄積されたステートメントを任意のタイミングでデータベースへフラッシュ(実行)するメソッドがあります。このメソッドは、 <code>ExecutorType</code> として <code>ExecutorType.BATCH</code> を使用している場合に使用することができます。</p>
250250
<source><![CDATA[List<BatchResult> flushStatements()]]></source>
251251

252-
<h5>トランザクションを制御するメソッド</h5>
252+
<h5 id="transaction-control-methods">トランザクションを制御するメソッド</h5>
253253
<p>トランザクションのスコープを制御するメソッドは4つあります。当然ですが、auto-commit を使用する場合や、外部のトランザクションマネージャーを使っている場合、これらのメソッドは効果がありません。しかし、Connection のインスタンスによって管理されている JDBC トランザクションマネージャーを利用している場合は便利なメソッドです。</p>
254254
<source>void commit()
255255
void commit(boolean force)
256256
void rollback()
257257
void rollback(boolean force)</source>
258-
<p>デフォルトでは、データベースが insert, update, delete メソッドの実行によって変更されない限り MyBatis は commit を実行しません。何らかの理由でこれらのメソッドを使わずにデータを変更した場合は確実にコミットされるように commit メソッドに引数 true を渡してください(ただし、auto-commit モードのセッションや外部のトランザクションマネージャーを使っている場合は true を渡してもコミットされません)。commit が実行されない場合、MyBatis がロールバックを実行するので、通常明示的に rollback() メソッドを呼び出す必要はありません。しかし、一つのセッションの中で複数のコミットやロールバックが必要とされるようなケースでは、rollback() メソッドを使ってより細かい制御を行うことが可能です。</p>
258+
<p>デフォルトでは、データベースの変更を伴うメソッド insert, update, delete, <code>affectData</code> を有効化した select が実行されない限り MyBatis は commit を実行しません。何らかの理由でこれらのメソッドを使わずにデータを変更した場合は確実にコミットされるように commit メソッドに引数 true を渡してください(ただし、auto-commit モードのセッションや外部のトランザクションマネージャーを使っている場合は true を渡してもコミットされません)。commit が実行されない場合、MyBatis がロールバックを実行するので、通常明示的に rollback() メソッドを呼び出す必要はありません。しかし、一つのセッションの中で複数のコミットやロールバックが必要とされるようなケースでは、rollback() メソッドを使ってより細かい制御を行うことが可能です。</p>
259259
<p><span class="label important">NOTE</span> Mybatis-Spring と MyBatis-Guice では宣言的トランザクションがサポートされています。詳細は各サブプロジェクトのドキュメントを参照してください。</p>
260260

261261
<h5>ローカルキャッシュ</h5>

src/site/ja/xdoc/sqlmap-xml.xml

+17
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ ps.setInt(1,id);]]></source>
233233
<td>複数の ResultSet を利用する場合にのみ有効です。ステートメントが返す ResultSet にそれぞれ任意の名前を付けてリストアップします。名前はカンマで区切ります。
234234
</td>
235235
</tr>
236+
<tr>
237+
<td><code>affectData</code></td>
238+
<td>ResultSet を返す INSERT, UPDATE, DELETE 文を記述する場合に true をセットします。これによりトランザクション制御が正しく実行されるようになります。<a href="./java-api.html#transaction-control-methods">トランザクションを制御するメソッド</a> も参照してください。 デフォルト: <code>false</code> (3.5.12 以降)
239+
</td>
240+
</tr>
236241
</tbody>
237242
</table>
238243
</subsection>
@@ -453,6 +458,18 @@ ps.setInt(1,id);]]></source>
453458
</tr>
454459
</tbody>
455460
</table>
461+
462+
<p>
463+
例外として、INSERT, UPDATE, DELETE 文から ResultSet を返す SQL 文(PostgreSQL, MariaDB の <code>RETURNING</code> , MS SQL Server の <code>OUTPUT</code> など)で結果をマップするためには <code><![CDATA[<select />]]></code> を使用する必要があります。
464+
</p>
465+
466+
<source><![CDATA[<select id="insertAndGetAuthor" resultType="domain.blog.Author"
467+
affectData="true" flushCache="true">
468+
insert into Author (username, password, email, bio)
469+
values (#{username}, #{password}, #{email}, #{bio})
470+
returning id, username, password, email, bio
471+
</select>]]></source>
472+
456473
</subsection>
457474

458475
<subsection name="sql">

src/site/ko/xdoc/java-api.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,15 @@ public interface ResultHandler<T> {
316316
이 방법은 <code>ExecutorType</code>을 <code>ExecutorType.BATCH</code>로 설정한 경우 사용가능하다. </p>
317317
<source><![CDATA[List<BatchResult> flushStatements()]]></source>
318318

319-
<h5>트랙잭션 제어 메소드</h5>
319+
<h5 id="transaction-control-methods">트랙잭션 제어 메소드</h5>
320320
<p>트랜잭션을 제어하기 위해 4개의 메소드가 있다.
321321
물론 자동커밋을 선택하였거나 외부 트랜잭션 관리자를 사용하면 영향이 없다.
322322
어쨌든 Connection인스턴스에 의해 관리되고 JDBC 트랜잭션 관리자를 사용하면 이 4개의 메소드를 사용할 수 있다.</p>
323323
<source>void commit()
324324
void commit(boolean force)
325325
void rollback()
326326
void rollback(boolean force)</source>
327-
<p>기본적으로 마이바티스는 insert, update 또는 delete 를 호출하여 데이터베이스가 변경된 것으로 감지하지 않는 한 실제로 커밋하지 않는다.
327+
<p>기본적으로 마이바티스는 insert, update, delete 또는 <code>affectData</code>가 활성화된select 를 호출하여 데이터베이스가 변경된 것으로 감지하지 않는 한 실제로 커밋하지 않는다.
328328
이러한 메소드 호출없이 변경되면 커밋된 것으로 보장하기 위해 commit 와 rollback 메소드에 true 값을 전달한다.</p>
329329
<p><span class="label important">참고</span> MyBatis-Spring 과 MyBatis-Guice는 선언적인 트랜잭션 관리기법을 제공한다.
330330
그래서 스프링이나 쥬스와 함께 마이바티스를 사용한다면 해당되는 메뉴얼을 꼭 참고하길 바란다. </p>

0 commit comments

Comments
 (0)