Skip to content

Commit

Permalink
Merge pull request #2741 from harawata/dirty-select
Browse files Browse the repository at this point in the history
Add `affectData` attribute to `@Select`, `@SelectProvider` and `<select />`
  • Loading branch information
harawata committed Nov 28, 2022
2 parents 92ea0e7 + 9e786dc commit 0b59460
Show file tree
Hide file tree
Showing 23 changed files with 475 additions and 18 deletions.
9 changes: 9 additions & 0 deletions src/main/java/org/apache/ibatis/annotations/Select.java
Expand Up @@ -55,6 +55,15 @@
*/
String databaseId() default "";

/**
* Returns whether this select affects DB data.<br>
* 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
Expand Down
Expand Up @@ -99,6 +99,15 @@
*/
String databaseId() default "";

/**
* Returns whether this select affects DB data.<br>
* 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
Expand Down
Expand Up @@ -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");
Expand All @@ -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) {
Expand Down Expand Up @@ -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> T valueOrDefault(T value, T defaultValue) {
Expand Down
Expand Up @@ -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());
});
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -672,13 +673,15 @@ private class AnnotationWrapper {
private final Annotation annotation;
private final String databaseId;
private final SqlCommandType sqlCommandType;
private boolean dirtySelect;

AnnotationWrapper(Annotation annotation) {
super();
this.annotation = annotation;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -723,5 +727,9 @@ SqlCommandType getSqlCommandType() {
String getDatabaseId() {
return databaseId;
}

boolean isDirtySelect() {
return dirtySelect;
}
}
}
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/apache/ibatis/mapping/MappedStatement.java
Expand Up @@ -56,6 +56,7 @@ public final class MappedStatement {
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
private boolean dirtySelect;

MappedStatement() {
// constructor disabled
Expand Down Expand Up @@ -174,6 +175,11 @@ public Builder resultSets(String resultSet) {
return this;
}

public Builder dirtySelect(boolean dirtySelect) {
mappedStatement.dirtySelect = dirtySelect;
return this;
}

/**
* Resul sets.
*
Expand Down Expand Up @@ -290,6 +296,10 @@ public String[] getResultSets() {
return resultSets;
}

public boolean isDirtySelect() {
return dirtySelect;
}

/**
* Gets the resul sets.
*
Expand Down
Expand Up @@ -120,6 +120,7 @@ public <T> Cursor<T> selectCursor(String statement, Object parameter) {
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
dirty |= ms.isDirtySelect();
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
Expand Down Expand Up @@ -148,6 +149,7 @@ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowB
private <E> List<E> 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);
Expand Down
Expand Up @@ -184,6 +184,7 @@ databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED
affectData (true|false) #IMPLIED
>

<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
Expand Down
4 changes: 2 additions & 2 deletions src/site/es/xdoc/java-api.xml
Expand Up @@ -245,13 +245,13 @@ public interface ResultHandler<T> {
<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>
<source><![CDATA[List<BatchResult> flushStatements()]]></source>

<h5>Métodos de control de transacción</h5>
<h5 id="transaction-control-methods">Métodos de control de transacción</h5>
<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>
<source>void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)</source>
<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>
<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>
<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>

<h5>Local Cache</h5>
Expand Down
17 changes: 17 additions & 0 deletions src/site/es/xdoc/sqlmap-xml.xml
Expand Up @@ -201,6 +201,11 @@ ps.setInt(1,id);]]></source>
<code>false</code>.
</td>
</tr>
<tr>
<td><code>affectData</code></td>
<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)
</td>
</tr>
</tbody>
</table>
</subsection>
Expand Down Expand Up @@ -405,6 +410,18 @@ Por ejemplo, si la columna id de la tabla Author del ejemplo siguiente fuera aut
</tr>
</tbody>
</table>

<p>
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.
</p>

<source><![CDATA[<select id="insertAndGetAuthor" resultType="domain.blog.Author"
affectData="true" flushCache="true">
insert into Author (username, password, email, bio)
values (#{username}, #{password}, #{email}, #{bio})
returning id, username, password, email, bio
</select>]]></source>

</subsection>

<subsection name="sql">
Expand Down
4 changes: 2 additions & 2 deletions src/site/ja/xdoc/java-api.xml
Expand Up @@ -249,13 +249,13 @@ public interface ResultHandler<T> {
<p>バッチ更新用に JDBC ドライバ内に蓄積されたステートメントを任意のタイミングでデータベースへフラッシュ(実行)するメソッドがあります。このメソッドは、 <code>ExecutorType</code> として <code>ExecutorType.BATCH</code> を使用している場合に使用することができます。</p>
<source><![CDATA[List<BatchResult> flushStatements()]]></source>

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

<h5>ローカルキャッシュ</h5>
Expand Down
17 changes: 17 additions & 0 deletions src/site/ja/xdoc/sqlmap-xml.xml
Expand Up @@ -233,6 +233,11 @@ ps.setInt(1,id);]]></source>
<td>複数の ResultSet を利用する場合にのみ有効です。ステートメントが返す ResultSet にそれぞれ任意の名前を付けてリストアップします。名前はカンマで区切ります。
</td>
</tr>
<tr>
<td><code>affectData</code></td>
<td>ResultSet を返す INSERT, UPDATE, DELETE 文を記述する場合に true をセットします。これによりトランザクション制御が正しく実行されるようになります。<a href="./java-api.html#transaction-control-methods">トランザクションを制御するメソッド</a> も参照してください。 デフォルト: <code>false</code> (3.5.12 以降)
</td>
</tr>
</tbody>
</table>
</subsection>
Expand Down Expand Up @@ -453,6 +458,18 @@ ps.setInt(1,id);]]></source>
</tr>
</tbody>
</table>

<p>
例外として、INSERT, UPDATE, DELETE 文から ResultSet を返す SQL 文(PostgreSQL, MariaDB の <code>RETURNING</code> , MS SQL Server の <code>OUTPUT</code> など)で結果をマップするためには <code><![CDATA[<select />]]></code> を使用する必要があります。
</p>

<source><![CDATA[<select id="insertAndGetAuthor" resultType="domain.blog.Author"
affectData="true" flushCache="true">
insert into Author (username, password, email, bio)
values (#{username}, #{password}, #{email}, #{bio})
returning id, username, password, email, bio
</select>]]></source>

</subsection>

<subsection name="sql">
Expand Down
4 changes: 2 additions & 2 deletions src/site/ko/xdoc/java-api.xml
Expand Up @@ -316,15 +316,15 @@ public interface ResultHandler<T> {
이 방법은 <code>ExecutorType</code>을 <code>ExecutorType.BATCH</code>로 설정한 경우 사용가능하다. </p>
<source><![CDATA[List<BatchResult> flushStatements()]]></source>

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

0 comments on commit 0b59460

Please sign in to comment.