org.dbflute.s2dao.sqlhandler.TnAbstractBasicSqlHandler Maven / Gradle / Ivy
/*
* Copyright 2014-2020 the original author or authors.
*
* 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.dbflute.s2dao.sqlhandler;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import javax.sql.DataSource;
import org.dbflute.bhv.core.context.ConditionBeanContext;
import org.dbflute.bhv.core.context.InternalMapContext;
import org.dbflute.bhv.core.context.ResourceContext;
import org.dbflute.bhv.exception.SQLExceptionHandler;
import org.dbflute.bhv.exception.SQLExceptionResource;
import org.dbflute.cbean.ConditionBean;
import org.dbflute.hook.CallbackContext;
import org.dbflute.hook.SqlFireHook;
import org.dbflute.hook.SqlFireReadyInfo;
import org.dbflute.hook.SqlFireResultInfo;
import org.dbflute.hook.SqlLogHandler;
import org.dbflute.hook.SqlLogInfo;
import org.dbflute.hook.SqlLogInfo.SqlLogDisplaySqlBuilder;
import org.dbflute.hook.SqlResultHandler;
import org.dbflute.jdbc.DataSourceHandler;
import org.dbflute.jdbc.ExecutionTimeInfo;
import org.dbflute.jdbc.HandlingDataSourceWrapper;
import org.dbflute.jdbc.ManualThreadDataSourceHandler;
import org.dbflute.jdbc.NotClosingConnectionWrapper;
import org.dbflute.jdbc.StatementFactory;
import org.dbflute.jdbc.ValueType;
import org.dbflute.s2dao.valuetype.TnValueTypes;
import org.dbflute.system.DBFluteSystem;
import org.dbflute.system.QLog;
import org.dbflute.twowaysql.DisplaySqlBuilder;
import org.dbflute.twowaysql.style.BoundDateDisplayStyle;
import org.dbflute.twowaysql.style.BoundDateDisplayTimeZoneProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The basic handler to execute SQL.
* All SQL executions of DBFlute are under this handler.
* This is always created when executing so it's non thread safe.
* @author modified by jflute (originated in S2Dao)
*/
public abstract class TnAbstractBasicSqlHandler {
// ===================================================================================
// Definition
// ==========
/** Log instance for internal debug. (QLog should be used instead for query log) */
private static final Logger _log = LoggerFactory.getLogger(TnAbstractBasicSqlHandler.class);
// ===================================================================================
// Attribute
// =========
protected final DataSource _dataSource;
protected final StatementFactory _statementFactory;
protected final String _sql;
protected Object[] _exceptionMessageSqlArgs; // not required
// ===================================================================================
// Constructor
// ===========
/**
* Constructor.
* @param dataSource The data source for a database connection. (NotNull)
* @param statementFactory The factory of statement. (NotNull)
* @param sql The executed SQL. (NotNull)
*/
public TnAbstractBasicSqlHandler(DataSource dataSource, StatementFactory statementFactory, String sql) {
assertObjectNotNull("dataSource", dataSource);
assertObjectNotNull("statementFactory", statementFactory);
assertObjectNotNull("sql", sql);
_dataSource = dataSource;
_statementFactory = statementFactory;
_sql = sql;
}
// ===================================================================================
// Common Logic
// ============
// -----------------------------------------------------
// Arguments Handling
// ------------------
/**
* @param conn The connection for the database. (NotNull)
* @param ps The prepared statement for the SQL. (NotNull)
* @param args The arguments for binding. (NullAllowed)
* @param valueTypes The types of binding value. (NotNull)
*/
protected void bindArgs(Connection conn, PreparedStatement ps, Object[] args, ValueType[] valueTypes) {
if (args == null) {
return;
}
Object current = null;
try {
for (int i = 0; i < args.length; ++i) {
final ValueType valueType = valueTypes[i];
current = args[i];
valueType.bindValue(conn, ps, i + 1, current);
}
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to bind the value.");
if (current != null) {
resource.addResource("Bound Value", current);
}
handleSQLException(e, resource);
}
}
/**
* @param conn The connection for the database. (NotNull)
* @param ps The prepared statement for the SQL. (NotNull)
* @param args The arguments for binding. (NullAllowed)
* @param argTypes The types of arguments. (NullAllowed: if args is null, this is also null)
*/
protected void bindArgs(Connection conn, PreparedStatement ps, Object[] args, Class>[] argTypes) {
bindArgs(conn, ps, args, argTypes, 0);
}
/**
* @param conn The connection for the database. (NotNull)
* @param ps The prepared statement for the SQL. (NotNull)
* @param args The arguments for binding. (NullAllowed)
* @param argTypes The types of arguments. (NullAllowed: if args is null, this is also null)
* @param beginIndex The index for beginning of binding.
*/
protected void bindArgs(Connection conn, PreparedStatement ps, Object[] args, Class>[] argTypes, int beginIndex) {
if (args == null) {
return;
}
Object current = null;
try {
for (int i = beginIndex; i < args.length; ++i) {
current = args[i];
final ValueType valueType = findValueType(argTypes[i], current);
valueType.bindValue(conn, ps, i + 1, current);
}
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to bind the value.");
if (current != null) {
resource.addResource("Bound Value", current);
}
handleSQLException(e, resource);
}
}
protected ValueType findValueType(Class> type, Object instance) {
return TnValueTypes.findByTypeOrValue(type, instance);
}
protected Class>[] getArgTypes(Object[] args) {
if (args == null) {
return null;
}
final Class>[] argTypes = new Class[args.length];
for (int i = 0; i < args.length; ++i) {
Object arg = args[i];
if (arg != null) {
argTypes[i] = arg.getClass();
}
}
return argTypes;
}
// -----------------------------------------------------
// SQL Logging
// -----------
protected void logSql(Object[] args, Class>[] argTypes) {
final boolean logEnabled = isLogEnabled();
final boolean hasSqlFireHook = hasSqlFireHook();
final boolean hasSqlLog = hasSqlLogHandler();
final boolean hasSqlResult = hasSqlResultHandler();
if (logEnabled || hasSqlFireHook || hasSqlLog || hasSqlResult) {
if (isInternalDebugEnabled()) {
final String determination = logEnabled + ", " + hasSqlFireHook + ", " + hasSqlLog + ", " + hasSqlResult;
_log.debug("...Logging SQL by " + determination);
}
if (processBeforeLogging(args, argTypes, logEnabled, hasSqlFireHook, hasSqlLog, hasSqlResult)) {
return; // processed by anyone
}
doLogSql(args, argTypes, logEnabled, hasSqlFireHook, hasSqlLog, hasSqlResult);
}
}
protected boolean processBeforeLogging(Object[] args, Class>[] argTypes, boolean logEnabled, boolean hasSqlFireHook,
boolean hasSqlLog, boolean hasSqlResult) {
return false;
}
protected void doLogSql(Object[] args, Class>[] argTypes, boolean logEnabled, boolean hasSqlFireHook, boolean hasSqlLog,
boolean hasSqlResult) {
final String firstDisplaySql;
if (logEnabled) { // build at once
if (isInternalDebugEnabled()) {
_log.debug("...Building DisplaySql by " + logEnabled);
}
firstDisplaySql = buildDisplaySql(_sql, args);
if (logEnabled) {
logDisplaySql(firstDisplaySql);
}
} else {
firstDisplaySql = null;
}
if (hasSqlFireHook || hasSqlLog || hasSqlResult) { // build lazily
if (isInternalDebugEnabled()) {
_log.debug("...Handling SqlFireHook or SqlLog or SqlResult by " + hasSqlFireHook + ", " + hasSqlLog + ", " + hasSqlResult);
}
final SqlLogInfo sqlLogInfo = prepareSqlLogInfo(args, argTypes, firstDisplaySql);
if (sqlLogInfo != null) { // basically true (except override)
if (hasSqlLog) {
getSqlLogHander().handle(sqlLogInfo);
}
if (hasSqlFireHook) {
saveHookSqlLogInfo(sqlLogInfo);
}
if (hasSqlResult) {
saveResultSqlLogInfo(sqlLogInfo);
}
}
}
}
// -----------------------------------------------------
// DisplaySql
// ----------
protected void logDisplaySql(String displaySql) {
log((isContainsLineSeparatorInSql(displaySql) ? ln() : "") + displaySql);
}
protected boolean isContainsLineSeparatorInSql(String displaySql) {
return displaySql != null ? displaySql.contains(ln()) : false;
}
protected String buildDisplaySql(String sql, Object[] args) {
return createDisplaySqlBuilder().buildDisplaySql(sql, args);
}
protected String getBindVariableText(Object bindVariable) { // basically for sub-class
return createDisplaySqlBuilder().getBindVariableText(bindVariable);
}
protected DisplaySqlBuilder createDisplaySqlBuilder() {
final BoundDateDisplayStyle realStyle;
final BoundDateDisplayStyle specifiedStyle = getSpecifiedLogDateDisplayStyle();
if (specifiedStyle != null) {
realStyle = specifiedStyle;
} else {
realStyle = createResourcedLogDateDisplayStyle();
}
return newDisplaySqlBuilder(realStyle);
}
protected BoundDateDisplayStyle getSpecifiedLogDateDisplayStyle() {
if (ConditionBeanContext.isExistConditionBeanOnThread()) {
final ConditionBean cb = ConditionBeanContext.getConditionBeanOnThread();
final BoundDateDisplayStyle specifiedStyle = cb.getLogDateDisplayStyle();
if (specifiedStyle != null) {
return specifiedStyle;
}
}
return null;
}
protected BoundDateDisplayStyle createResourcedLogDateDisplayStyle() {
final String datePattern = ResourceContext.getLogDatePattern();
final String iimestampPattern = ResourceContext.getLogTimestampPattern();
final String timePattern = ResourceContext.getLogTimePattern();
final BoundDateDisplayTimeZoneProvider timeZoneProvider = ResourceContext.getLogTimeZoneProvider();
return new BoundDateDisplayStyle(datePattern, iimestampPattern, timePattern, timeZoneProvider);
}
protected DisplaySqlBuilder newDisplaySqlBuilder(BoundDateDisplayStyle dateDisplayStyle) {
return new DisplaySqlBuilder(dateDisplayStyle);
}
// -----------------------------------------------------
// SqlFireHook
// -----------
protected SqlFireHook getSqlFireHook() {
if (!CallbackContext.isExistCallbackContextOnThread()) {
return null;
}
return CallbackContext.getCallbackContextOnThread().getSqlFireHook();
}
protected boolean hasSqlFireHook() {
return getSqlFireHook() != null;
}
protected void saveHookSqlLogInfo(SqlLogInfo sqlLogInfo) {
InternalMapContext.setHookSqlLogInfo(sqlLogInfo);
}
// -----------------------------------------------------
// SqlLogHandler
// -------------
protected SqlLogHandler getSqlLogHander() {
if (!CallbackContext.isExistCallbackContextOnThread()) {
return null;
}
return CallbackContext.getCallbackContextOnThread().getSqlLogHandler();
}
protected boolean hasSqlLogHandler() {
return getSqlLogHander() != null;
}
protected SqlLogInfo prepareSqlLogInfo(Object[] args, Class>[] argTypes, String alreadyBuiltDisplaySql) {
final SqlLogDisplaySqlBuilder sqlLogDisplaySqlBuilder = createSqlLogDisplaySqlBuilder(alreadyBuiltDisplaySql);
return new SqlLogInfo(ResourceContext.behaviorCommand(), _sql, args, argTypes, sqlLogDisplaySqlBuilder);
}
protected SqlLogDisplaySqlBuilder createSqlLogDisplaySqlBuilder(final String alreadyBuiltDisplaySql) {
if (alreadyBuiltDisplaySql != null) {
return (executedSql, bindArgs, bindArgTypes) -> {
if (isInternalDebugEnabled()) {
_log.debug("...Returning DisplaySql, already built");
}
return alreadyBuiltDisplaySql;
};
} else {
return (executedSql, bindArgs, bindArgTypes) -> {
if (isInternalDebugEnabled()) {
_log.debug("...Building DisplaySql lazily");
}
return buildDisplaySql(executedSql, bindArgs);
};
}
}
// -----------------------------------------------------
// SqlResultHandler
// ----------------
protected SqlResultHandler getSqlResultHander() {
if (!CallbackContext.isExistCallbackContextOnThread()) {
return null;
}
return CallbackContext.getCallbackContextOnThread().getSqlResultHandler();
}
protected boolean hasSqlResultHandler() {
return getSqlResultHander() != null;
}
protected void saveResultSqlLogInfo(SqlLogInfo sqlLogInfo) {
InternalMapContext.setResultSqlLogInfo(sqlLogInfo);
}
// ===================================================================================
// Exception Handler
// =================
protected void handleSQLException(SQLException e, SQLExceptionResource resource) {
resource.setExecutedSql(_sql);
resource.setDisplaySql(buildExceptionMessageSql());
createSQLExceptionHandler().handleSQLException(e, resource);
}
protected SQLExceptionHandler createSQLExceptionHandler() {
return ResourceContext.createSQLExceptionHandler();
}
protected SQLExceptionResource createSQLExceptionResource() {
return new SQLExceptionResource();
}
protected String buildExceptionMessageSql() {
String displaySql = null;
if (_sql != null && _exceptionMessageSqlArgs != null) {
try {
displaySql = buildDisplaySql(_sql, _exceptionMessageSqlArgs);
} catch (RuntimeException continued) { // because of when exception occurs
if (_log.isDebugEnabled()) {
_log.debug("*Failed to build SQL for an exception message: " + continued.getMessage());
}
}
}
return displaySql;
}
// ===================================================================================
// JDBC Handling
// =============
// -----------------------------------------------------
// Connection
// ----------
/**
* Get the database connection from data source.
* getting connection for SQL executions is only here.
* (for meta data is at TnBeanMetaDataFactoryImpl)
* @return The new-created or inherited instance of connection. (NotNull)
*/
protected Connection getConnection() {
try {
final ManualThreadDataSourceHandler handler = getManualThreadDataSourceHandler();
if (handler != null) {
return handler.getConnection(_dataSource);
}
final Connection conn = _dataSource.getConnection();
return conn;
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to get database connection.");
handleSQLException(e, resource);
return null; // unreachable
}
}
/**
* Get the data source handler of manual thread.
* @return The instance of the data source handler. (NullAllowed: if null, no manual thread handling)
*/
protected ManualThreadDataSourceHandler getManualThreadDataSourceHandler() {
return ManualThreadDataSourceHandler.getDataSourceHandler();
}
/**
* @param conn The instance of connection for the statement. (NotNull)
* @return The new-created prepared statement. (NotNull)
*/
protected PreparedStatement prepareStatement(Connection conn) {
if (_sql == null) {
throw new IllegalStateException("The SQL should not be null.");
}
return _statementFactory.createPreparedStatement(conn, _sql);
}
/**
* @param conn The instance of connection for the statement. (NotNull)
* @return The new-created call-able statement. (NotNull)
*/
protected CallableStatement prepareCall(final Connection conn) {
if (_sql == null) {
throw new IllegalStateException("The SQL should not be null.");
}
return _statementFactory.createCallableStatement(conn, _sql);
}
/**
* Create the data source (wrapper) to inherit connection.
* @param conn The instance of connection to be wrapped. (NotNull)
* @return The new-created wrapper for data source handling. (NotNull)
*/
protected HandlingDataSourceWrapper createInheritedConnectionDataSource(final Connection conn) {
return new HandlingDataSourceWrapper(_dataSource, new DataSourceHandler() {
public Connection getConnection(DataSource dataSource) throws SQLException {
return new NotClosingConnectionWrapper(conn); // not keep actual if closed (is default)
}
});
}
// -----------------------------------------------------
// Execution
// ---------
// _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
// four super stars:
// o executeQuery()
// o executeUpdate()
// o executeBatch()
// o executeProcedure()
// _/_/_/_/_/_/_/_/_/_/
protected ResultSet executeQuery(PreparedStatement ps) throws SQLException {
final boolean saveMillis = isSaveMillis();
if (saveMillis) {
saveBeforeSqlTimeMillis();
}
hookSqlFireBefore();
ResultSet rs = null;
SQLException nativeCause = null;
try {
rs = ps.executeQuery();
if (saveMillis) {
saveAfterSqlTimeMillis();
}
return rs;
} catch (SQLException e) {
nativeCause = e;
throw e;
} finally {
hookSqlFireFinally(rs, nativeCause);
}
}
protected int executeUpdate(PreparedStatement ps) { // with SQLException handling
final boolean saveMillis = isSaveMillis();
if (saveMillis) {
saveBeforeSqlTimeMillis();
}
hookSqlFireBefore();
Integer updated = null;
SQLException nativeCause = null;
try {
updated = ps.executeUpdate();
if (saveMillis) {
saveAfterSqlTimeMillis();
}
return updated;
} catch (SQLException e) {
nativeCause = e;
final SQLExceptionResource resource = createSQLExceptionResource();
final String processTitle = getUpdateSQLFailureProcessTitle();
resource.setNotice("Failed to execute the SQL for " + processTitle + ".");
resource.enableUniqueConstraintHandling();
handleSQLException(e, resource);
return -1; // unreachable
} finally {
hookSqlFireFinally(updated, nativeCause);
}
}
protected String getUpdateSQLFailureProcessTitle() {
return "update (non-select)"; // as default
}
protected int[] executeBatch(PreparedStatement ps, List> list) {
final boolean saveMillis = isSaveMillis();
if (saveMillis) {
saveBeforeSqlTimeMillis();
}
hookSqlFireBefore();
int[] batchResult = null;
SQLException nativeCause = null;
try {
batchResult = ps.executeBatch();
if (saveMillis) {
saveAfterSqlTimeMillis();
}
return batchResult;
} catch (SQLException e) {
nativeCause = e;
final SQLExceptionResource resource = createSQLExceptionResource();
final String processTitle = getBatchUpdateSQLFailureProcessTitle();
resource.setNotice("Failed to execute the SQL for " + processTitle + ".");
resource.enableUniqueConstraintHandling();
resource.enableDisplaySqlPartHandling();
handleSQLException(e, resource);
return null; // unreachable
} finally {
hookSqlFireFinally(batchResult, nativeCause);
}
}
protected String getBatchUpdateSQLFailureProcessTitle() {
return "batch update (non-select)";
}
protected void addBatch(PreparedStatement ps) {
try {
ps.addBatch();
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to add the batch statement.");
resource.enableUniqueConstraintHandling();
handleSQLException(e, resource);
}
}
protected boolean executeProcedure(CallableStatement cs) throws SQLException {
final boolean saveMillis = isSaveMillis();
if (saveMillis) {
saveBeforeSqlTimeMillis();
}
hookSqlFireBefore();
Boolean executed = null;
SQLException nativeCause = null;
try {
executed = cs.execute();
if (saveMillis) {
saveAfterSqlTimeMillis();
}
return executed;
} catch (SQLException e) {
nativeCause = e;
throw e;
} finally {
hookSqlFireFinally(executed, nativeCause);
}
}
// -----------------------------------------------------
// SaveMillis
// ----------
protected boolean isSaveMillis() {
return hasSqlFireHook() || hasSqlResultHandler();
}
protected void saveBeforeSqlTimeMillis() {
InternalMapContext.setSqlBeforeTimeMillis(systemTime());
}
protected void saveAfterSqlTimeMillis() {
InternalMapContext.setSqlAfterTimeMillis(systemTime());
}
// -----------------------------------------------------
// SqlFireHook
// -----------
protected void hookSqlFireBefore() {
if (!hasSqlFireHook()) {
return;
}
final SqlLogInfo sqlLogInfo = InternalMapContext.getHookSqlLogInfo();
final SqlFireReadyInfo fireReadyInfo = new SqlFireReadyInfo(sqlLogInfo);
getSqlFireHook().hookBefore(ResourceContext.behaviorCommand(), fireReadyInfo);
}
protected void hookSqlFireFinally(Object nativeResult, SQLException nativeCause) {
if (!hasSqlFireHook()) {
return;
}
final SqlLogInfo sqlLogInfo = InternalMapContext.getHookSqlLogInfo();
final Long sqlBefore = InternalMapContext.getSqlBeforeTimeMillis();
final Long sqlAfter = InternalMapContext.getSqlAfterTimeMillis();
final ExecutionTimeInfo timeInfo = new ExecutionTimeInfo(null, null, sqlBefore, sqlAfter);
final SqlFireResultInfo fireResultInfo = new SqlFireResultInfo(nativeResult, sqlLogInfo, timeInfo, nativeCause);
getSqlFireHook().hookFinally(ResourceContext.behaviorCommand(), fireResultInfo);
}
// -----------------------------------------------------
// JDBC Option
// -----------
protected void setFetchSize(Statement st, int fetchSize) {
if (st == null) {
return;
}
try {
st.setFetchSize(fetchSize);
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to set fetch size.");
resource.addResource("Fetch Size", fetchSize);
handleSQLException(e, resource);
}
}
protected void setMaxRows(Statement st, int maxRows) {
if (st == null) {
return;
}
try {
st.setMaxRows(maxRows);
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to set max rows.");
resource.addResource("Max Rows", maxRows);
handleSQLException(e, resource);
}
}
// -----------------------------------------------------
// Close
// -----
protected void close(Statement st) {
if (st == null) {
return;
}
try {
st.close();
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to close the statement.");
handleSQLException(e, resource);
}
}
protected void close(ResultSet resultSet) {
if (resultSet == null) {
return;
}
try {
resultSet.close();
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to close the result set.");
handleSQLException(e, resource);
}
}
protected void close(Connection conn) {
if (conn == null) {
return;
}
try {
conn.close();
} catch (SQLException e) {
final SQLExceptionResource resource = createSQLExceptionResource();
resource.setNotice("Failed to close the database connection.");
handleSQLException(e, resource);
}
}
// ===================================================================================
// Query Log
// =========
protected boolean isLogEnabled() {
return QLog.isLogEnabled();
}
protected void log(String msg) {
QLog.log(msg);
}
// ===================================================================================
// Internal Debug
// ==============
protected boolean isInternalDebugEnabled() { // because log instance is private
return ResourceContext.isInternalDebug() && _log.isDebugEnabled();
}
// ===================================================================================
// Assert Helper
// =============
protected void assertObjectNotNull(String variableName, Object value) {
if (variableName == null) {
String msg = "The value should not be null: variableName=null value=" + value;
throw new IllegalArgumentException(msg);
}
if (value == null) {
String msg = "The value should not be null: variableName=" + variableName;
throw new IllegalArgumentException(msg);
}
}
// ===================================================================================
// General Helper
// ==============
protected String ln() {
return DBFluteSystem.ln();
}
protected long systemTime() {
return DBFluteSystem.currentTimeMillis(); // for calculating performance
}
// ===================================================================================
// Accessor
// ========
public void setExceptionMessageSqlArgs(Object[] exceptionMessageSqlArgs) {
this._exceptionMessageSqlArgs = exceptionMessageSqlArgs;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy