/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * 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 * * http://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.seasar.extension.jdbc.query; import java.sql.PreparedStatement; import java.util.List; import javax.persistence.OptimisticLockException; import org.seasar.extension.jdbc.BatchUpdate; import org.seasar.extension.jdbc.DbmsDialect; import org.seasar.extension.jdbc.EntityMeta; import org.seasar.extension.jdbc.JdbcContext; import org.seasar.extension.jdbc.exception.SEntityExistsException; import org.seasar.extension.jdbc.exception.SOptimisticLockException; import org.seasar.extension.jdbc.manager.JdbcManagerImplementor; import org.seasar.framework.exception.EmptyRuntimeException; import org.seasar.framework.util.PreparedStatementUtil; import org.seasar.framework.util.StatementUtil; /** * バッチ更新の抽象クラスです。 * * @author koichik * @param <T> * エンティティの型です。 * @param <S> * <code>BatchUpdate</code>のサブタイプです。 */ public abstract class AbstractAutoBatchUpdate<T, S extends BatchUpdate<S>> extends AbstractQuery<S> implements BatchUpdate<S> { /** エンティティのリスト */ protected final List<T> entities; /** エンティティメタデータ */ protected final EntityMeta entityMeta; /** バッチサイズ */ protected int batchSize; /** * バージョンチェックを行った場合に、 更新行数が0行でも{@link OptimisticLockException}をスローしないなら * <code>true</code> */ protected boolean suppresOptimisticLockException; /** * @param jdbcManager * 内部的なJDBCマネージャ * @param entities * エンティティのリスト * @throws EmptyRuntimeException * エンティティのリストが空の場合 */ public AbstractAutoBatchUpdate(final JdbcManagerImplementor jdbcManager, final List<T> entities) { super(jdbcManager); if (entities == null) { throw new NullPointerException("entities"); } if (entities.isEmpty()) { throw new EmptyRuntimeException("entities"); } this.entities = entities; entityMeta = jdbcManager.getEntityMetaFactory().getEntityMeta( entities.get(0).getClass()); } @SuppressWarnings("unchecked") public S batchSize(final int batchSize) { this.batchSize = batchSize; return (S) this; } public int[] execute() { prepare("executeBatch"); try { return executeInternal(); } catch (final RuntimeException e) { if (getJdbcManager().getDialect().isUniqueConstraintViolation(e)) { throw new SEntityExistsException(executedSql, e); } throw e; } finally { completed(); } } /** * エンティティのリストを返します。 * * @return エンティティのリスト */ public List<T> getEntities() { return entities; } /** * SQLを準備します。 */ protected void prepareSql() { executedSql = toSql(); } /** * SQLに変換します。 * * @return SQL */ protected abstract String toSql(); /** * データベースのバッチ更新を実行します。 * * @return 更新した行数の配列 */ protected int[] executeInternal() { final JdbcContext jdbcContext = jdbcManager.getJdbcContext(); try { final PreparedStatement ps = getPreparedStatement(jdbcContext); final int[] rows = executeBatch(ps); if (entityMeta.hasVersionPropertyMeta()) { incrementVersions(); } return rows; } finally { if (!jdbcContext.isTransactional()) { jdbcContext.destroy(); } } } /** * バッチ更新を実行します。 * * @param ps * 準備されたステートメント * @return 更新された行数の配列 */ protected int[] executeBatch(final PreparedStatement ps) { final DbmsDialect dialect = jdbcManager.getDialect(); final int batchSize = this.batchSize > 0 ? this.batchSize : dialect .getDefaultBatchSize(); final int size = entities.size(); final int[] updateRows = new int[size]; int pos = 0; for (int i = 0; i < size; ++i) { final T entity = entities.get(i); prepareParams(entity); logSql(); prepareInParams(ps); PreparedStatementUtil.addBatch(ps); resetParams(); if (i == size - 1 || (batchSize > 0 && (i + 1) % batchSize == 0)) { final int[] rows = PreparedStatementUtil.executeBatch(ps); if (isOptimisticLock()) { validateRows(ps, rows); } System.arraycopy(rows, 0, updateRows, pos, rows.length); pos = i + 1; } } return updateRows; } /** * 準備されたステートメントを返します。 * * @param jdbcContext * JDBCコンテキスト * @return 準備されたステートメント */ protected PreparedStatement getPreparedStatement( final JdbcContext jdbcContext) { final PreparedStatement ps = createPreparedStatement(jdbcContext); if (queryTimeout > 0) { StatementUtil.setQueryTimeout(ps, queryTimeout); } prepareInParams(ps); return ps; } /** * 準備されたステートメントを作成します。 * * @param jdbcContext * JDBCコンテキスト * @return 準備されたステートメント */ protected PreparedStatement createPreparedStatement( final JdbcContext jdbcContext) { return jdbcContext.getPreparedStatement(executedSql); } /** * バインド変数を準備します. * * @param entity * エンティティ */ protected abstract void prepareParams(T entity); /** * 楽観的同時実行制御を行っている場合は<code>true</code>を返します。 * * @return 楽観的同時実行制御を行っている場合は<code>true</code> */ protected abstract boolean isOptimisticLock(); /** * 行を更新または削除できたかどうかチェックします。 * * @param ps * 準備されたステートメント * @param rows * 更新行数の配列 * @throws OptimisticLockException * 行を更新または削除できなかった場合 */ protected void validateRows(final PreparedStatement ps, final int[] rows) { if (suppresOptimisticLockException) { return; } final DbmsDialect dialect = jdbcManager.getDialect(); if (dialect.supportsBatchUpdateResults()) { for (int i = 0; i < rows.length; ++i) { if (rows[i] != 1) { throw new SOptimisticLockException(entities.get(i)); } } } else if (StatementUtil.getUpdateCount(ps) == rows.length) { for (int i = 0; i < rows.length; ++i) { rows[i] = 1; } } else { throw new SOptimisticLockException(); } } /** * バージョンの値を増加させます。 */ protected void incrementVersions() { } }