/*
* 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.manager;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.TransactionSynchronizationRegistry;
import org.seasar.extension.datasource.DataSourceFactory;
import org.seasar.extension.datasource.impl.SelectableDataSourceProxy;
import org.seasar.extension.jdbc.AutoBatchDelete;
import org.seasar.extension.jdbc.AutoBatchInsert;
import org.seasar.extension.jdbc.AutoBatchUpdate;
import org.seasar.extension.jdbc.AutoDelete;
import org.seasar.extension.jdbc.AutoFunctionCall;
import org.seasar.extension.jdbc.AutoInsert;
import org.seasar.extension.jdbc.AutoProcedureCall;
import org.seasar.extension.jdbc.AutoSelect;
import org.seasar.extension.jdbc.AutoUpdate;
import org.seasar.extension.jdbc.DbmsDialect;
import org.seasar.extension.jdbc.EntityMeta;
import org.seasar.extension.jdbc.EntityMetaFactory;
import org.seasar.extension.jdbc.JdbcContext;
import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.extension.jdbc.SqlBatchUpdate;
import org.seasar.extension.jdbc.SqlFileBatchUpdate;
import org.seasar.extension.jdbc.SqlFileFunctionCall;
import org.seasar.extension.jdbc.SqlFileProcedureCall;
import org.seasar.extension.jdbc.SqlFileSelect;
import org.seasar.extension.jdbc.SqlFileUpdate;
import org.seasar.extension.jdbc.SqlFunctionCall;
import org.seasar.extension.jdbc.SqlProcedureCall;
import org.seasar.extension.jdbc.SqlSelect;
import org.seasar.extension.jdbc.SqlUpdate;
import org.seasar.extension.jdbc.exception.NoIdPropertyRuntimeException;
import org.seasar.extension.jdbc.query.AutoBatchDeleteImpl;
import org.seasar.extension.jdbc.query.AutoBatchInsertImpl;
import org.seasar.extension.jdbc.query.AutoBatchUpdateImpl;
import org.seasar.extension.jdbc.query.AutoDeleteImpl;
import org.seasar.extension.jdbc.query.AutoFunctionCallImpl;
import org.seasar.extension.jdbc.query.AutoInsertImpl;
import org.seasar.extension.jdbc.query.AutoProcedureCallImpl;
import org.seasar.extension.jdbc.query.AutoSelectImpl;
import org.seasar.extension.jdbc.query.AutoUpdateImpl;
import org.seasar.extension.jdbc.query.SqlBatchUpdateImpl;
import org.seasar.extension.jdbc.query.SqlFileBatchUpdateImpl;
import org.seasar.extension.jdbc.query.SqlFileFunctionCallImpl;
import org.seasar.extension.jdbc.query.SqlFileProcedureCallImpl;
import org.seasar.extension.jdbc.query.SqlFileSelectImpl;
import org.seasar.extension.jdbc.query.SqlFileUpdateImpl;
import org.seasar.extension.jdbc.query.SqlFunctionCallImpl;
import org.seasar.extension.jdbc.query.SqlProcedureCallImpl;
import org.seasar.extension.jdbc.query.SqlSelectImpl;
import org.seasar.extension.jdbc.query.SqlUpdateImpl;
import org.seasar.extension.jdbc.util.DataSourceUtil;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.convention.PersistenceConvention;
import org.seasar.framework.exception.EmptyRuntimeException;
/**
* {@link JdbcManager}の実装クラスです。
*
* @author higa
*
*
*/
public class JdbcManagerImpl implements JdbcManager, JdbcManagerImplementor {
/**
* トランザクション同期レジストリです。
*/
protected TransactionSynchronizationRegistry syncRegistry;
/**
* データソースのファクトリです。
*/
protected DataSourceFactory dataSourceFactory;
/**
* データソースです。
*/
protected DataSource dataSource;
/**
* データベースの方言です。
*/
protected DbmsDialect dialect;
/**
* エンティティメタデータファクトリです。
*/
protected EntityMetaFactory entityMetaFactory;
/**
* 永続化層の規約です。
*/
protected PersistenceConvention persistenceConvention;
/**
* デフォルトの最大行数です。
*/
protected int maxRows = 0;
/**
* デフォルトのフェッチサイズです。
*/
protected int fetchSize = 0;
/**
* デフォルトのクエリタイムアウトです。
*/
protected int queryTimeout = 0;
/**
* バッチ更新で可変のSQLを許可する場合は<code>true</code>です。
*/
protected boolean allowVariableSqlForBatchUpdate = true;
public <T> AutoSelect<T> from(Class<T> baseClass) {
return new AutoSelectImpl<T>(this, baseClass).maxRows(maxRows)
.fetchSize(fetchSize).queryTimeout(queryTimeout);
}
public <T> SqlSelect<T> selectBySql(Class<T> baseClass, String sql,
Object... params) {
return new SqlSelectImpl<T>(this, baseClass, sql, params).maxRows(
maxRows).fetchSize(fetchSize).queryTimeout(queryTimeout);
}
public long getCountBySql(String sql, Object... params) {
return new SqlSelectImpl<Long>(this, Long.class, sql, params)
.getCount();
}
public <T> SqlFileSelect<T> selectBySqlFile(Class<T> baseClass, String path) {
return selectBySqlFile(baseClass, path, null);
}
public <T> SqlFileSelect<T> selectBySqlFile(Class<T> baseClass,
String path, Object parameter) {
return new SqlFileSelectImpl<T>(this, baseClass, path, parameter)
.maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
queryTimeout);
}
public long getCountBySqlFile(String path) {
return new SqlFileSelectImpl<Long>(this, Long.class, path, null)
.getCount();
}
public long getCountBySqlFile(String path, Object parameter) {
return new SqlFileSelectImpl<Long>(this, Long.class, path, parameter)
.getCount();
}
public <T> AutoInsert<T> insert(final T entity) {
return new AutoInsertImpl<T>(this, entity).queryTimeout(queryTimeout);
}
public <T> AutoBatchInsert<T> insertBatch(final T... entities) {
return new AutoBatchInsertImpl<T>(this, Arrays.asList(entities))
.queryTimeout(queryTimeout);
}
public <T> AutoBatchInsert<T> insertBatch(final List<T> entities) {
return new AutoBatchInsertImpl<T>(this, entities)
.queryTimeout(queryTimeout);
}
public <T> AutoUpdate<T> update(final T entity) {
final EntityMeta entityMeta = entityMetaFactory.getEntityMeta(entity
.getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0761", entityMeta
.getName());
}
return new AutoUpdateImpl<T>(this, entity).queryTimeout(queryTimeout);
}
public <T> AutoBatchUpdate<T> updateBatch(final T... entities) {
if (entities == null) {
throw new NullPointerException("entities");
}
if (entities.length == 0) {
throw new EmptyRuntimeException("entities");
}
final EntityMeta entityMeta = entityMetaFactory
.getEntityMeta(entities[0].getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0761", entityMeta
.getName());
}
return new AutoBatchUpdateImpl<T>(this, Arrays.asList(entities))
.queryTimeout(queryTimeout);
}
public <T> AutoBatchUpdate<T> updateBatch(final List<T> entities) {
if (entities == null) {
throw new NullPointerException("entities");
}
if (entities.isEmpty()) {
throw new EmptyRuntimeException("entities");
}
final EntityMeta entityMeta = entityMetaFactory.getEntityMeta(entities
.get(0).getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0761", entityMeta
.getName());
}
return new AutoBatchUpdateImpl<T>(this, entities)
.queryTimeout(queryTimeout);
}
public SqlUpdate updateBySql(String sql, Class<?>... paramClasses) {
return new SqlUpdateImpl(this, sql, paramClasses)
.queryTimeout(queryTimeout);
}
public SqlBatchUpdate updateBatchBySql(String sql, Class<?>... paramClasses) {
return new SqlBatchUpdateImpl(this, sql, paramClasses)
.queryTimeout(queryTimeout);
}
public SqlFileUpdate updateBySqlFile(String path) {
return new SqlFileUpdateImpl(this, path).queryTimeout(queryTimeout);
}
public SqlFileUpdate updateBySqlFile(String path, Object parameter) {
return new SqlFileUpdateImpl(this, path, parameter)
.queryTimeout(queryTimeout);
}
public <T> SqlFileBatchUpdate<T> updateBatchBySqlFile(String path,
List<T> params) {
return new SqlFileBatchUpdateImpl<T>(this, path, params)
.queryTimeout(queryTimeout);
}
public <T> SqlFileBatchUpdate<T> updateBatchBySqlFile(String path,
T... params) {
return new SqlFileBatchUpdateImpl<T>(this, path, Arrays.asList(params))
.queryTimeout(queryTimeout);
}
public <T> AutoDelete<T> delete(final T entity) {
final EntityMeta entityMeta = entityMetaFactory.getEntityMeta(entity
.getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0762", entityMeta
.getName());
}
return new AutoDeleteImpl<T>(this, entity).queryTimeout(queryTimeout);
}
public <T> AutoBatchDelete<T> deleteBatch(final T... entities) {
if (entities == null) {
throw new NullPointerException("entities");
}
if (entities.length == 0) {
throw new EmptyRuntimeException("entities");
}
final EntityMeta entityMeta = entityMetaFactory
.getEntityMeta(entities[0].getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0762", entityMeta
.getName());
}
return new AutoBatchDeleteImpl<T>(this, Arrays.asList(entities))
.queryTimeout(queryTimeout);
}
public <T> AutoBatchDelete<T> deleteBatch(final List<T> entities) {
if (entities == null) {
throw new NullPointerException("entities");
}
if (entities.isEmpty()) {
throw new EmptyRuntimeException("entities");
}
final EntityMeta entityMeta = entityMetaFactory.getEntityMeta(entities
.get(0).getClass());
if (entityMeta.getIdPropertyMetaList().isEmpty()) {
throw new NoIdPropertyRuntimeException("ESSR0762", entityMeta
.getName());
}
return new AutoBatchDeleteImpl<T>(this, entities)
.queryTimeout(queryTimeout);
}
public AutoProcedureCall call(String procedureName) {
return call(procedureName, null);
}
public AutoProcedureCall call(String procedureName, Object parameter) {
return new AutoProcedureCallImpl(this, procedureName, parameter)
.maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
queryTimeout);
}
public SqlProcedureCall callBySql(String sql) {
return callBySql(sql, null);
}
public SqlProcedureCall callBySql(String sql, Object parameter) {
return new SqlProcedureCallImpl(this, sql, parameter).maxRows(maxRows)
.fetchSize(fetchSize).queryTimeout(queryTimeout);
}
public SqlFileProcedureCall callBySqlFile(String path) {
return callBySqlFile(path, null);
}
public SqlFileProcedureCall callBySqlFile(String path, Object parameter) {
return new SqlFileProcedureCallImpl(this, path, parameter).maxRows(
maxRows).fetchSize(fetchSize).queryTimeout(queryTimeout);
}
public <T> AutoFunctionCall<T> call(Class<T> resultClass,
String functionName) {
return call(resultClass, functionName, null);
}
public <T> AutoFunctionCall<T> call(Class<T> resultClass,
String functionName, Object parameter) {
return new AutoFunctionCallImpl<T>(this, resultClass, functionName,
parameter).maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
queryTimeout);
}
public <T> SqlFunctionCall<T> callBySql(Class<T> resultClass, String sql) {
return callBySql(resultClass, sql, null);
}
public <T> SqlFunctionCall<T> callBySql(Class<T> resultClass, String sql,
Object parameter) {
return new SqlFunctionCallImpl<T>(this, resultClass, sql, parameter)
.maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
queryTimeout);
}
public <T> SqlFileFunctionCall<T> callBySqlFile(Class<T> resultClass,
String path) {
return callBySqlFile(resultClass, path, null);
}
public <T> SqlFileFunctionCall<T> callBySqlFile(Class<T> resultClass,
String path, Object parameter) {
return new SqlFileFunctionCallImpl<T>(this, resultClass, path,
parameter).maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
queryTimeout);
}
/**
* JDBCコンテキストを返します。
*
* @return JDBCコンテキスト
*/
public JdbcContext getJdbcContext() {
JdbcContext ctx = getTxBoundJdbcContext();
if (ctx != null) {
return ctx;
}
Connection con = DataSourceUtil.getConnection(dataSource);
if (hasTransaction()) {
ctx = createJdbcContext(con, true);
setTxBoundJdbcContext(ctx);
} else {
ctx = createJdbcContext(con, false);
}
return ctx;
}
public String getSelectableDataSourceName() {
return getSelectableDataSourceNameInternal();
}
/**
* 動的なデータソース名を返します。
*
* @return 存在する場合は動的なデータソース名、存在しない場合は <code>null</code>
*/
protected String getSelectableDataSourceNameInternal() {
if (dataSource instanceof SelectableDataSourceProxy) {
if (dataSourceFactory != null) {
return dataSourceFactory.getSelectableDataSourceName();
}
}
return null;
}
/**
* 現在のトランザクションに関連づけられたJDBCコンテキストを返します。
*
* @return 現在のトランザクションに関連づけられたJDBCコンテキスト
*/
protected JdbcContext getTxBoundJdbcContext() {
if (hasTransaction()) {
final JdbcContextRegistryKey key = createJdbcContextRegistryKey();
final JdbcContext ctx = JdbcContext.class.cast(syncRegistry
.getResource(key));
if (ctx != null) {
return ctx;
}
}
return null;
}
/**
* 現在のトランザクションにJDBCコンテキストを関連づけます。
*
* @param ctx
* 現在のトランザクションに関連づけるJDBCコンテキスト
*/
protected void setTxBoundJdbcContext(final JdbcContext ctx) {
final JdbcContextRegistryKey key = createJdbcContextRegistryKey();
syncRegistry.putResource(key, ctx);
syncRegistry.registerInterposedSynchronization(new SynchronizationImpl(
ctx));
}
/**
* JDBCコンテキストの登録キーを作成します。
*
* @return JDBCコンテキストの登録キー
*/
protected JdbcContextRegistryKey createJdbcContextRegistryKey() {
return new JdbcContextRegistryKey(getSelectableDataSourceNameInternal());
}
/**
* 現在のスレッドでトランザクションが開始されていれば<code>true</code>を返します。
*
* @return 現在のスレッドでトランザクションが開始されていれば<code>true</code>
*/
protected boolean hasTransaction() {
final int status = syncRegistry.getTransactionStatus();
return status != Status.STATUS_NO_TRANSACTION
&& status != Status.STATUS_UNKNOWN;
}
/**
* JDBCコンテキストがnullかどうかを返します。
*
* @return JDBCコンテキストがnullかどうか
*/
protected boolean isJdbcContextNull() {
return getTxBoundJdbcContext() == null;
}
/**
* JDBCコンテキストを作成します。
*
* @param connection
* コネクション
* @param transactional
* トランザクション中かどうか
* @return JDBCコンテキスト
*/
protected JdbcContext createJdbcContext(Connection connection,
boolean transactional) {
return new JdbcContextImpl(connection, transactional);
}
/**
* トランザクション同期レジストリを返します。
*
* @return トランザクション同期レジストリ
*/
public TransactionSynchronizationRegistry getSyncRegistry() {
return syncRegistry;
}
/**
* トランザクション同期レジストリを設定します。
*
* @param syncRegistry
* トランザクション同期レジストリ
*/
public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) {
this.syncRegistry = syncRegistry;
}
/**
* データソースを返します。
*
* @return データソース
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* データソースを設定します。
*
* @param dataSource
* データソース
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* デフォルトのフェッチ数を返します。
*
* @return デフォルトのフェッチ数
*/
public int getFetchSize() {
return fetchSize;
}
/**
* デフォルトのフェッチ数を設定します。
*
* @param fetchSize
* デフォルトのフェッチ数
*/
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
/**
* デフォルトの最大行数を返します。
*
* @return デフォルトの最大行数
*/
public int getMaxRows() {
return maxRows;
}
/**
* デフォルトの最大行数を設定します。
*
* @param maxRows
* デフォルトの最大行数
*/
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
/**
* デフォルトのクエリタイムアウトを返します。
*
* @return デフォルトのクエリタイムアウト
*/
public int getQueryTimeout() {
return queryTimeout;
}
/**
* デフォルトのクエリタイムアウトを設定します。
*
* @param queryTimeout
* デフォルトのクエリタイムアウト
*/
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
public boolean isAllowVariableSqlForBatchUpdate() {
return allowVariableSqlForBatchUpdate;
}
/**
* バッチ更新で可変のSQLを許可する場合は<code>true</code>、しない場合は<code>false</code>を設定します。
*
* @param allowVariableSqlForBatchUpdate
* バッチ更新で可変のSQLを許可する場合は<code>true</code>、しない場合は<code>false</code>
*/
public void setAllowVariableSqlForBatchUpdate(
boolean allowVariableSqlForBatchUpdate) {
this.allowVariableSqlForBatchUpdate = allowVariableSqlForBatchUpdate;
}
/**
* データソースファクトリを返します。
*
* @return 存在する場合はデータソースファクトリ、存在しない場合は <code>null</code>
*/
public DataSourceFactory getDataSourceFactory() {
return dataSourceFactory;
}
/**
* データソースファクトリを設定します。
*
* @param dataSourceFactory
*/
@Binding(bindingType = BindingType.MAY)
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
}
/**
* データベースの方言を返します。
*
* @return データベースの方言
*/
public DbmsDialect getDialect() {
return dialect;
}
/**
* データベースの方言を設定します。
*
* @param dialect
* データベースの方言
*/
public void setDialect(DbmsDialect dialect) {
this.dialect = dialect;
}
/**
* エンティティメタデータファクトリを返します。
*
* @return エンティティメタデータファクトリ
*/
public EntityMetaFactory getEntityMetaFactory() {
return entityMetaFactory;
}
/**
* エンティティメタデータファクトリを設定します。
*
* @param entityMetaFactory
* エンティティメタデータファクトリ
*/
public void setEntityMetaFactory(EntityMetaFactory entityMetaFactory) {
this.entityMetaFactory = entityMetaFactory;
}
public PersistenceConvention getPersistenceConvention() {
return persistenceConvention;
}
/**
* 永続化層の規約を設定します。
*
* @param persistenceConvention
* 永続化層の規約
*/
public void setPersistenceConvention(
PersistenceConvention persistenceConvention) {
this.persistenceConvention = persistenceConvention;
}
/**
* {@link Synchronization}の実装です。
*
* @author koichik
*/
public class SynchronizationImpl implements Synchronization {
/** JDBCコンテキスト */
protected final JdbcContext context;
/**
* インスタンスを構築します。
*
* @param context
* JDBCコンテキスト
*/
public SynchronizationImpl(final JdbcContext context) {
this.context = context;
}
public final void beforeCompletion() {
}
public void afterCompletion(final int status) {
context.destroy();
}
}
/**
* JDBCコンテキストを{@link TransactionSynchronizationRegistry}に登録する際のキーです。
*
* @author taedium
*/
public class JdbcContextRegistryKey {
/** データソース名 */
protected String dataSourceName;
/**
* インスタンスを構築します。
*
* @param dataSourceName
* データソース名
*/
public JdbcContextRegistryKey(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime
* result
+ ((dataSourceName == null) ? 0 : dataSourceName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JdbcContextRegistryKey other = (JdbcContextRegistryKey) obj;
if (!getOuterType().equals(other.getOuterType())) {
return false;
}
if (dataSourceName == null) {
if (other.dataSourceName != null) {
return false;
}
} else if (!dataSourceName.equals(other.dataSourceName)) {
return false;
}
return true;
}
private JdbcManagerImpl getOuterType() {
return JdbcManagerImpl.this;
}
}
}