diff --git a/docs/index.adoc b/docs/index.adoc index 8793b4ba5..7edee8045 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -27,4 +27,5 @@ include::how2entity/index.adoc[leveloffset=+1] include::metamodel/index.adoc[leveloffset=+1] include::transaction/index.adoc[leveloffset=+1] include::event/index.adoc[leveloffset=+1] +include::showsql/index.adoc[leveloffset=+1] include::release/index.adoc[leveloffset=+1] diff --git a/docs/setup/properties.adoc b/docs/setup/properties.adoc index 4441c71ae..756d130e7 100644 --- a/docs/setup/properties.adoc +++ b/docs/setup/properties.adoc @@ -47,4 +47,25 @@ | ``sqlmapper.sql-template.encoding`` | ``UTF-8`` | SQLテンプレートのファイルの文字コード。 -|=== \ No newline at end of file +|=== + +.SQLログ出力の設定 +|=== +| キー | デフォルト値 | 説明 + +| ``sqlmapper.show-sql.enabled`` +| ``false`` +| 実行するSQLをログ出力するかどうか。``true`` のときログ出力します。 + +| ``sqlmapper.show-sql.log-level`` +| ``DEBUG`` +| SQLをログ出力するときのログレベル。Slf4jのログレベル( `TRACE` / `DEBUG` / `INFO` / `WARN` / `ERROR` ) の何れかを指定できます。 + +| ``sqlmapper.show-sql.bind-param.enabled`` +| ``false`` +| 実行するSQLのバインド変数をログ出力するかどうか。``true`` のときログ出力します。 + +| ``sqlmapper.show-sql.bind-param.log-level`` +| ``DEBUG`` +| SQLのバインド変数をログ出力するときのログレベル。Slf4jのログレベル( `TRACE` / `DEBUG` / `INFO` / `WARN` / `ERROR` ) の何れかを指定できます。 +|=== diff --git a/docs/showsql/index.adoc b/docs/showsql/index.adoc new file mode 100644 index 000000000..86bcc525d --- /dev/null +++ b/docs/showsql/index.adoc @@ -0,0 +1,44 @@ +[[showsql]] += SQLのログ出力 + +* 実行するSQLをログ出力したい場合は、プロパティファイルにて機能の有効に設定します。 +** 詳細は、<> を参照して設定変更をしてください。 +* クラス `com.github.mygreen.sqlmapper.core.query.SqlLogger` に対してログ出力設定を追加します。 +** ログレベルは、プロパティファイルに定義した値と対応する必要があります。 +** ログレベルの初期値は、`DEBUG` です。 + +.SQLのログ出力の有効化 +[source,yaml] +---- +sqlmapper: + show-sql: + enabled: true # ログ出力機能を有効にします。 + bind-param: + enabled: true # バインド変数のログ出力機能を有効にします。 +---- + +.SQLのロガー設定(例.logback.xml) +[source,xml] +---- + + + + + .%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n + + + DEBUG + + + + + + + + + + + + + +---- diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/SqlMapperContext.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/SqlMapperContext.java index f97cb0f50..e77cd6c65 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/SqlMapperContext.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/SqlMapperContext.java @@ -15,6 +15,7 @@ import com.github.mygreen.sqlmapper.core.meta.EntityMetaFactory; import com.github.mygreen.sqlmapper.core.meta.StoredParamMetaFactory; import com.github.mygreen.sqlmapper.core.naming.NamingRule; +import com.github.mygreen.sqlmapper.core.query.SqlLogger; import com.github.mygreen.sqlmapper.core.type.ValueTypeRegistry; import lombok.Getter; @@ -87,6 +88,11 @@ public class SqlMapperContext { */ private SqlTemplateEngine sqlTemplateEngine; + /** + * SQLログ出力 + */ + private SqlLogger sqlLogger; + /** * トランザクションの伝搬タイプが {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW} のトランザクションテンプレートを作成します。 *

ID生成用のトランザクションテンプレートとして使用します。 diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/StoredName.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/StoredName.java index f3f17cf0c..af9c3c796 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/StoredName.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/StoredName.java @@ -1,5 +1,7 @@ package com.github.mygreen.sqlmapper.core; +import com.github.mygreen.sqlmapper.core.util.NameUtils; + import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -7,6 +9,7 @@ * ストアドプロシージャ/ストアドファンクションの名称を指定するためのクラス。 *

スキーマ/カタログを指定する際に利用します。 * + * @version 0.4 * @since 0.3 * @author T.TSUCHIE * @@ -52,4 +55,13 @@ public StoredName withCatalog(String catalog) { return this; } + /** + * スキーマ名/カタログ名を考慮したフルネームを取得します。 + * @since 0.4 + * @return フルネームを取得します。 + */ + public String toFullName() { + return NameUtils.tableFullName(name, catalog, schema); + } + } diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/ShowSqlProperties.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/ShowSqlProperties.java new file mode 100644 index 000000000..ee5a555da --- /dev/null +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/ShowSqlProperties.java @@ -0,0 +1,43 @@ +package com.github.mygreen.sqlmapper.core.config; + +import org.slf4j.event.Level; + +import lombok.Data; + +/** + * SQLをログに出力する設定。 + * + * @since 0.4 + * @author T.TSUCHIE + * + */ +@Data +public class ShowSqlProperties { + + /** SQLのログ出力機能を有効にするかどうか。 */ + private boolean enabled; + + /** SQL出力時のログレベル */ + private Level logLevel; + + /** SQLのバインド変数に関するSQLの出力設定 */ + private BindParamProperties bindParam; + + /** + * SQLログ出力のバインドパラメータ設定 + * + * @since 0.4 + * @author T.TSUCHIE + * + */ + @Data + public static class BindParamProperties { + + /** バインド変数を出力するかどうか */ + private boolean enabled; + + /** バインド変数出力時のログレベル */ + private Level LogLevel; + } + +} diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/SqlMapperConfigurationSupport.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/SqlMapperConfigurationSupport.java index 217f4d3d4..a265749de 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/SqlMapperConfigurationSupport.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/config/SqlMapperConfigurationSupport.java @@ -2,6 +2,7 @@ import javax.sql.DataSource; +import org.slf4j.event.Level; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -26,6 +27,7 @@ import com.github.mygreen.sqlmapper.core.SqlMapper; import com.github.mygreen.sqlmapper.core.SqlMapperContext; import com.github.mygreen.sqlmapper.core.audit.AuditingEntityListener; +import com.github.mygreen.sqlmapper.core.config.ShowSqlProperties.BindParamProperties; import com.github.mygreen.sqlmapper.core.dialect.Dialect; import com.github.mygreen.sqlmapper.core.meta.EntityMetaFactory; import com.github.mygreen.sqlmapper.core.meta.PropertyMetaFactory; @@ -33,6 +35,7 @@ import com.github.mygreen.sqlmapper.core.meta.StoredPropertyMetaFactory; import com.github.mygreen.sqlmapper.core.naming.DefaultNamingRule; import com.github.mygreen.sqlmapper.core.naming.NamingRule; +import com.github.mygreen.sqlmapper.core.query.SqlLogger; import com.github.mygreen.sqlmapper.core.type.ValueTypeRegistry; /** @@ -93,6 +96,7 @@ public SqlMapperContext sqlMapperContext() { context.setDataSource(dataSource()); context.setJdbcTemplateProperties(jdbcTemplateProperties()); context.setTransactionManager(transactionManager()); + context.setSqlLogger(sqlLogger()); return context; @@ -137,6 +141,22 @@ public TableIdGeneratorProperties tableIdGeneratorProperties() { return prop; } + @Bean + @Description("SQLのログ出力設定") + public ShowSqlProperties showSqlProperties() { + + ShowSqlProperties prop = new ShowSqlProperties(); + prop.setEnabled(Boolean.parseBoolean(env.getProperty("sqlmapper.show-sql.enabled"))); + prop.setLogLevel(Level.valueOf(env.getProperty("sqlmapper.show-sql.log-level").toUpperCase())); + + BindParamProperties bindProp = new BindParamProperties(); + bindProp.setEnabled(Boolean.parseBoolean(env.getProperty("sqlmapper.show-sql.bind-param.enabled"))); + bindProp.setLogLevel(Level.valueOf(env.getProperty("sqlmapper.show-sql.bind-param.log-level").toUpperCase())); + prop.setBindParam(bindProp); + + return prop; + } + @Bean @Description("エンティティの対応クラスからメタ情報を作成するBean。") public EntityMetaFactory entityMetaFactory() { @@ -238,4 +258,10 @@ public AuditingEntityListener auditingEntityListener() { return new AuditingEntityListener(); } + @Bean + @Description("SQLのログ出力処理") + public SqlLogger sqlLogger() { + return new SqlLogger(showSqlProperties()); + } + } diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/InsertClause.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/InsertClause.java index 96de4a2c1..2592bf3ec 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/InsertClause.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/InsertClause.java @@ -37,7 +37,7 @@ public InsertClause(final int capacity) { } /** - * INTO句をSQLに変換します。 + * INTO句({@literal (col1, col2...)をSQLに変換します。 * @return SQL */ public String toIntoSql() { @@ -45,7 +45,7 @@ public String toIntoSql() { } /** - * VALUES句をSQLに変換します。 + * VALUES句({@literal values (exp1, exp2, ...)} * @return SQL */ public String toValuesSql() { diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/SqlLogger.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/SqlLogger.java new file mode 100644 index 000000000..3d4f96caa --- /dev/null +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/SqlLogger.java @@ -0,0 +1,295 @@ +package com.github.mygreen.sqlmapper.core.query; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.slf4j.event.Level; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.jdbc.core.StatementCreatorUtils; +import org.springframework.util.CollectionUtils; + +import com.github.mygreen.sqlmapper.core.config.ShowSqlProperties; +import com.github.mygreen.sqlmapper.core.util.QueryUtils; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * SQLをログ出力する処理。 + * + * @since 0.4 + * @author T.TSUCHIE + * + */ +@Slf4j +@RequiredArgsConstructor +public class SqlLogger { + + /** + * SQLのログ出力設定 + * @return SQLのログ出力設定を取得します。 + */ + @Getter + private final ShowSqlProperties prop; + + private static final SimpleDateFormat FORMAT_SQL_TIME = new SimpleDateFormat("HH:mm:ss"); + + private static final SimpleDateFormat FORMAT_SQL_DATE = new SimpleDateFormat("yyyy-MM-dd"); + + private static final SimpleDateFormat FORMAT_SQL_TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + private static final SimpleDateFormat FORMAT_UTIL_DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + /** + * SQLをログ出力する。 + * @param sql 出力対象のSQL + * @param params SQLのバインド変数 + */ + public void out(final String sql, final Object[] params) { + outSql(sql); + if(prop.getBindParam().isEnabled() && !QueryUtils.isEmpty(params)) { + outParams(Arrays.asList(params)); + } + + } + + /** + * SQLをログ出力する。 + * @param sql 出力対象のSQL + * @param params SQLのバインド変数 + */ + public void out(final String sql, final Collection params) { + outSql(sql); + outParams(params); + } + + /** + * バッチ実行用のSQLをログ出力する。 + * @param sql 出力対象のSQL + * @param batchParams バッチ変数用SQLのバインド変数 + */ + public void outBatch(final String sql, final List batchParams) { + outSql(sql); + outBatchParams(batchParams); + + } + + /** + * ストアドプロシージャ/ストアドファンクションのログを出力する。 + * @param callName ストアドプロシージャ/ストアドファンクションのSQL + * @param callParams ストアドプロシージャ/ストアドファンクションのバインド変数 + */ + public void outCall(final String callName, final Object[] callParams) { + outCallName(callName, callParams); + outCallParams(callParams); + } + + /** + * SQLをログ出力する。 + * @param sql 出力対象のSQL + */ + private void outSql(final String sql) { + if(!prop.isEnabled()) { + return; + } + invokeLog(prop.getLogLevel(), "sql statement : {}", new Object[]{sql}); + } + + /** + * SQLのバインド変数をログ出力する。 + * @param params SQLのバインド変数 + */ + private void outParams(final Collection params) { + if(!prop.isEnabled() || !prop.getBindParam().isEnabled() || CollectionUtils.isEmpty(params)) { + return; + } + + int paramCount = 1; + for(Object param : params) { + String paramType = resolveBindParamType(param); + String paramValue = resolveBindParamValue(param); + + invokeLog(prop.getBindParam().getLogLevel(), "sql binding parameter : [{}] as [{}] - [{}]", + new Object[]{paramCount, paramType, paramValue }); + + paramCount++; + } + } + + /** + * バッチ実行用のSQLのバインド変数をログ出力する。 + * @param batchParams SQLのバインド変数 + */ + private void outBatchParams(final Collection batchParams) { + if(!prop.isEnabled() || !prop.getBindParam().isEnabled() || CollectionUtils.isEmpty(batchParams)) { + return; + } + + int recordCount = 1; + for(Object[] recordParams : batchParams) { + int paramCount = 1; + for(Object param : recordParams) { + String paramType = resolveBindParamType(param); + String paramValue = resolveBindParamValue(param); + + invokeLog(prop.getBindParam().getLogLevel(), "sql batch binding parameter : [{}][{}] as [{}] - [{}]", + new Object[]{recordCount, paramCount, paramType, paramValue }); + + paramCount++; + } + recordCount++; + } + } + + /** + * ストアドプロシージャ/ストアドファンクションの名称をログ出力する。 + * @param callName 名称 + * @param callParams パラメータ + */ + private void outCallName(final String callName, final Object[] callParams) { + if(!prop.isEnabled()) { + return; + } + + int paramCount = (callParams == null) ? 0 : callParams.length; + String args = QueryUtils.repeat("?", ", ", paramCount); + invokeLog(prop.getBindParam().getLogLevel(), "sql call : {}({})", new Object[]{callName, args}); + } + + /** + * ストアドプロシージャ/ストアドファンクションのパラメータをログ出力する。 + * @param callParams パラメータ + */ + private void outCallParams(final Object[] callParams) { + if(!prop.isEnabled() || !prop.getBindParam().isEnabled() || QueryUtils.isEmpty(callParams)) { + return; + } + + int paramCount = 1; + for(Object param : callParams) { + String paramType = resolveBindParamType(param); + String paramValue = resolveBindParamValue(param); + + invokeLog(prop.getBindParam().getLogLevel(), "sql call binding parameter : [{}] as [{}] - [{}]", + new Object[]{paramCount, paramType, paramValue }); + + paramCount++; + } + } + + + + /** + * ログレベルを指定してログに出力する。 + * @param level ログレベル。 + * @param message ログメッセージ。 + * @param args ログメッセージ中の引数。 + */ + private void invokeLog(Level level, String message, Object[] args) { + switch (level) { + case TRACE: + log.trace(message, args); + break; + case DEBUG: + log.debug(message, args); + break; + case INFO: + log.info(message, args); + break; + case WARN: + log.warn(message, args); + break; + case ERROR: + log.error(message, args); + break; + } + } + + /** + * SQLのバンド変数のログ出力用のタイプ名に変換する。 + * @param value バンド変数 + * @return タイプ情報。 + */ + private String resolveBindParamType(final Object value) { + if (value == null) { + return "NULL"; + } else if (value instanceof SqlParameterValue) { + return ((SqlParameterValue)(value)).getTypeName(); + } else { + int type = StatementCreatorUtils.javaTypeToSqlParameterType(value.getClass()); + switch (type) { + case Types.BOOLEAN: + return "BOOLEAN"; + case Types.TINYINT: + return "TINYINT"; + case Types.SMALLINT: + return "SMALLINT"; + case Types.INTEGER: + return "INTEGER"; + case Types.BIGINT: + return "BIGINT"; + case Types.FLOAT: + return "FLOAT"; + case Types.DOUBLE: + return "DOUBLE"; + case Types.DECIMAL: + return "DECIMAL"; + case Types.NUMERIC: + return "NUMERIC"; + case Types.BLOB: + return "BLOB"; + case Types.CLOB: + return "CLOB"; + case Types.CHAR: + return "CHAR"; + case Types.VARCHAR: + return "VARCHAR"; + case Types.DATE: + return "DATE"; + case Types.TIMESTAMP: + return "TIMESTAMP"; + default: + return "UNKNOWN"; + } + + } + } + + /** + * SQLのバインド変数をログ出力用の値に変換する。 + * @param value バインド変数。 + * @return ログ出力用の値。 + */ + private String resolveBindParamValue(final Object value) { + if (value == null) { + return "NULL"; + } else if (value instanceof Blob) { + return ""; + } else if(value instanceof Clob) { + return ""; + } else if(value instanceof Time) { + return FORMAT_SQL_TIME.format(value); + } else if(value instanceof Date) { + return FORMAT_SQL_DATE.format(value); + } else if(value instanceof Timestamp) { + return FORMAT_SQL_TIMESTAMP.format(value); + } else if(value instanceof java.util.Date) { + return FORMAT_UTIL_DATE.format(value); + } else if (value instanceof SqlParameterValue) { + return ((SqlParameterValue)(value)).getValue().toString(); + } else { + return value.toString(); + } + } + + +} diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoAnyDeleteExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoAnyDeleteExecutor.java index 442a6cb8e..7a025e795 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoAnyDeleteExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoAnyDeleteExecutor.java @@ -108,6 +108,8 @@ private void prepareSql() { */ public int execute() { prepare(); + context.getSqlLogger().out(executedSql, paramValues); + return getJdbcTemplate().update(executedSql, paramValues.toArray()); } diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchDeleteExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchDeleteExecutor.java index 8f5f10003..f0cd6b7f4 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchDeleteExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchDeleteExecutor.java @@ -143,6 +143,7 @@ private boolean isOptimisticLock() { public int execute() { prepare(); + context.getSqlLogger().out(executedSql, paramValues); final int rows = getJdbcTemplate().update(executedSql, paramValues.toArray()); if(isOptimisticLock()) { diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchInsertExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchInsertExecutor.java index 4dd24878b..5db2f0fab 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchInsertExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchInsertExecutor.java @@ -20,6 +20,7 @@ import com.github.mygreen.sqlmapper.core.id.IdentityIdGenerator; import com.github.mygreen.sqlmapper.core.meta.PropertyMeta; import com.github.mygreen.sqlmapper.core.meta.PropertyValueInvoker; +import com.github.mygreen.sqlmapper.core.query.InsertClause; import com.github.mygreen.sqlmapper.core.query.JdbcTemplateBuilder; import com.github.mygreen.sqlmapper.core.type.ValueType; import com.github.mygreen.sqlmapper.core.util.NumberConvertUtils; @@ -51,6 +52,11 @@ public class AutoBatchInsertExecutor { */ private final SqlMapperContext context; + /** + * INSERT句 - SQLログ出力のために使用する。 + */ + private InsertClause insertClause = new InsertClause(); + /** * クエリのパラメータ - エンティティごとの設定 */ @@ -153,6 +159,10 @@ private void prepareSqlParam() { // IDENTITYの主キーでない場合は通常カラムとして追加 if(!usingIdentityKeyColumnNames.contains(columnName)) { usingColumnNames.add(columnName); + + if(context.getSqlLogger().getProp().isEnabled()) { + insertClause.addSql(columnName, "?"); + } } } @@ -247,6 +257,19 @@ public int[] execute() { prepare(); + if(context.getSqlLogger().getProp().isEnabled()) { + String executedSql = "insert into " + + query.getEntityMeta().getTableMeta().getFullName() + + insertClause.toIntoSql() + + insertClause.toValuesSql(); + + List batchArgs = new ArrayList<>(batchParams.length); + for(MapSqlParameterSource paramSource : batchParams) { + batchArgs.add(paramSource.getValues().values().toArray()); + } + context.getSqlLogger().outBatch(executedSql, batchArgs); + } + if(this.usingIdentityKeyColumnNames.isEmpty()) { // 主キーがIDENTITYによる生成でない場合 return insertOperation.executeBatch(batchParams); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchUpdateExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchUpdateExecutor.java index a340063b1..23551b82d 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchUpdateExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoBatchUpdateExecutor.java @@ -235,7 +235,10 @@ public int[] execute() { return new int[query.getEntitySize()]; } - int[] res = getJdbcTemplate().batchUpdate(executedSql, QueryUtils.convertBatchArgs(batchParams)); + final List batchArgs = QueryUtils.convertBatchArgs(batchParams); + context.getSqlLogger().outBatch(executedSql, batchArgs); + + int[] res = getJdbcTemplate().batchUpdate(executedSql, batchArgs); final int dataSize = query.getEntitySize(); for(int i=0; i < dataSize; i++) { diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoDeleteExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoDeleteExecutor.java index fe7f3c2c5..0e2804b54 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoDeleteExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoDeleteExecutor.java @@ -133,6 +133,7 @@ private boolean isOptimisticLock() { public int execute() { prepare(); + context.getSqlLogger().out(executedSql, paramValues); final int rows = getJdbcTemplate().update(executedSql, paramValues.toArray()); if(isOptimisticLock()) { diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoFunctionCallImpl.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoFunctionCallImpl.java index 47bd8054f..84385bda3 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoFunctionCallImpl.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoFunctionCallImpl.java @@ -74,7 +74,6 @@ public AutoFunctionCallImpl queryTimeout(int seconds) { @Override public T execute() { - final SimpleJdbcCall jdbcCall = new SimpleJdbcCall(getJdbcTemplate()) .withFunctionName(functionName.getName()); @@ -88,6 +87,7 @@ public T execute() { if(parameter.isEmpty()) { + context.getSqlLogger().outCall(functionName.toFullName(), null); return jdbcCall.executeFunction(resultClass); } else { @@ -95,6 +95,8 @@ public T execute() { Object[] parameterValues = parameter.map(p -> createParameterValues(paramMeta, p)) .orElseGet(() -> new Object[0]); + context.getSqlLogger().outCall(functionName.toFullName(), parameterValues); + if(containsResultParam(paramMeta, parameter)) { Map out = jdbcCall.declareParameters(parameterTypes) .execute(parameterValues); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoInsertExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoInsertExecutor.java index ec0a13d46..9f8061a89 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoInsertExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoInsertExecutor.java @@ -19,6 +19,7 @@ import com.github.mygreen.sqlmapper.core.id.IdentityIdGenerator; import com.github.mygreen.sqlmapper.core.meta.PropertyMeta; import com.github.mygreen.sqlmapper.core.meta.PropertyValueInvoker; +import com.github.mygreen.sqlmapper.core.query.InsertClause; import com.github.mygreen.sqlmapper.core.query.JdbcTemplateBuilder; import com.github.mygreen.sqlmapper.core.type.ValueType; import com.github.mygreen.sqlmapper.core.util.NumberConvertUtils; @@ -50,6 +51,11 @@ public class AutoInsertExecutor { */ private final SqlMapperContext context; + /** + * INSERT句 - SQLログ出力のために使用する。 + */ + private InsertClause insertClause = new InsertClause(); + /** * クエリのパラメータです。 */ @@ -127,6 +133,10 @@ private void prepareSqlParam() { // IDENTITYの主キーでない場合は通常カラムとして追加 if(!usingIdentityKeyColumnNames.contains(columnName)) { usingColumnNames.add(columnName); + + if(context.getSqlLogger().getProp().isEnabled()) { + insertClause.addSql(columnName, "?"); + } } // クエリのパラメータの組み立て @@ -225,6 +235,15 @@ private JdbcTemplate getJdbcTemplate() { public int execute() { prepare(); + if(context.getSqlLogger().getProp().isEnabled()) { + String executedSql = "insert into " + + query.getEntityMeta().getTableMeta().getFullName() + + insertClause.toIntoSql() + + insertClause.toValuesSql(); + + context.getSqlLogger().out(executedSql, paramSource.getValues().values()); + } + if(this.usingIdentityKeyColumnNames.isEmpty()) { // 主キーがIDENTITYによる生成しない場合 return insertOperation.execute(paramSource); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoProcedureCallImpl.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoProcedureCallImpl.java index 36697d1e4..1211bf32b 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoProcedureCallImpl.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoProcedureCallImpl.java @@ -72,7 +72,6 @@ public AutoProcedureCallImpl queryTimeout(int seconds) { @Override public void execute() { - final SimpleJdbcCall jdbcCall = new SimpleJdbcCall(getJdbcTemplate()) .withProcedureName(procedureName.getName()); @@ -84,8 +83,8 @@ public void execute() { jdbcCall.withSchemaName(procedureName.getSchema()); } - if(parameter.isEmpty()) { + context.getSqlLogger().outCall(procedureName.toFullName(), null); jdbcCall.execute(); } else { @@ -93,6 +92,8 @@ public void execute() { Object[] parameterValues = parameter.map(p -> createParameterValues(paramMeta, p)) .orElseGet(() -> new Object[0]); + context.getSqlLogger().outCall(procedureName.toFullName(), parameterValues); + Map out = jdbcCall.declareParameters(parameterTypes) .execute(parameterValues); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoSelectExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoSelectExecutor.java index fc3c47485..2aea460aa 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoSelectExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoSelectExecutor.java @@ -1,632 +1,636 @@ -package com.github.mygreen.sqlmapper.core.query.auto; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.util.StringUtils; - -import com.github.mygreen.sqlmapper.core.SqlMapperContext; -import com.github.mygreen.sqlmapper.core.dialect.Dialect; -import com.github.mygreen.sqlmapper.core.mapper.AutoEntityRowMapper; -import com.github.mygreen.sqlmapper.core.mapper.EntityMappingCallback; -import com.github.mygreen.sqlmapper.core.meta.EntityMeta; -import com.github.mygreen.sqlmapper.core.meta.PropertyMeta; -import com.github.mygreen.sqlmapper.core.query.FromClause; -import com.github.mygreen.sqlmapper.core.query.IllegalOperateException; -import com.github.mygreen.sqlmapper.core.query.IllegalQueryException; -import com.github.mygreen.sqlmapper.core.query.JdbcTemplateBuilder; -import com.github.mygreen.sqlmapper.core.query.JoinAssociation; -import com.github.mygreen.sqlmapper.core.query.JoinCondition; -import com.github.mygreen.sqlmapper.core.query.OrderByClause; -import com.github.mygreen.sqlmapper.core.query.SelectClause; -import com.github.mygreen.sqlmapper.core.query.TableNameResolver; -import com.github.mygreen.sqlmapper.core.query.WhereClause; -import com.github.mygreen.sqlmapper.core.type.ValueType; -import com.github.mygreen.sqlmapper.core.where.metamodel.MetamodelWhere; -import com.github.mygreen.sqlmapper.core.where.metamodel.MetamodelWhereVisitor; -import com.github.mygreen.sqlmapper.core.where.simple.SimpleWhereBuilder; -import com.github.mygreen.sqlmapper.core.where.simple.SimpleWhereVisitor; -import com.github.mygreen.sqlmapper.metamodel.EntityPath; -import com.github.mygreen.sqlmapper.metamodel.OrderSpecifier; -import com.github.mygreen.sqlmapper.metamodel.Path; -import com.github.mygreen.sqlmapper.metamodel.PathMeta; -import com.github.mygreen.sqlmapper.metamodel.Predicate; -import com.github.mygreen.sqlmapper.metamodel.PropertyPath; - - -/** - * 抽出を行うSQLを自動生成するクエリを実行します。 - * {@link AutoSelectImpl}のクエリ実行処理の移譲先です。 - * - * - * @author T.TSUCHIE - * - * @param 処理対象となるエンティティの型 - */ -public class AutoSelectExecutor { - - /** - * クエリ情報 - */ - private final AutoSelectImpl query; - - /** - * 設定情報 - */ - private final SqlMapperContext context; - - /** - * SELECT COUNT(*)~で行数を取得する場合にtrue - */ - private final boolean counting; - - /** - * select句です。 - */ - private SelectClause selectClause = new SelectClause(); - - /** - * from句です。 - */ - private FromClause fromClause = new FromClause(); - - /** - * where句です。 - */ - private final WhereClause whereClause = new WhereClause(); - - /** - * order by句です。 - */ - private final OrderByClause orderByClause = new OrderByClause(); - - /** - * テーブルの別名を管理します。 - */ - private final TableNameResolver tableNameResolver = new TableNameResolver(); - - /** - * for update句です。 - */ - private String forUpdateClause; - - /** - * 実行するSQLです - */ - private String executedSql; - - /** - * クエリのパラメータです。 - */ - private final List paramValues = new ArrayList<>(); - - /** - * 抽出対象のプロパティ情報とプロパティのマッピング先のエンティティのタイプ情報 - */ - private Map> targetPropertyMetaEntityTypeMap; - - /** - * インスタンスの作成 - * - * @param query クエリ情報 - * @param counting カウント用のクエリかどうか - */ - public AutoSelectExecutor(AutoSelectImpl query, boolean counting) { - this.query = query; - this.counting = counting; - this.context = query.getContext(); - } - - /** - * クエリ実行の準備を行います。 - */ - private void prepare() { - - prepareTableAlias(); - prepareTargetColumn(); - prepareTargetTable(); - prepareIdVersion(); - prepareCondition(); - prepareOrderBy(); - prepareForUpdate(); - - prepareSql(); - } - - /** - * テーブルの別名を準備します。 - */ - private void prepareTableAlias() { - - // FROM句指定のテーブル - tableNameResolver.prepareTableAlias(query.getEntityPath()); - - // JOINテーブルのエイリアス - for(JoinCondition condition : query.getJoinConditions()) { - tableNameResolver.prepareTableAlias(condition.getToEntity()); - } - - // 構成定義のバリデーション - for(JoinAssociation association : query.getJoinAssociations()) { - validateJoinAssociation(association); - } - } - - /** - * 構成定義が抽出対象のテーブルのエンティティかどうか - * - * @param association 構成定義情報 - */ - private void validateJoinAssociation(JoinAssociation association) { - - // 参照対象のエンティティかチェックする - boolean foundEntity1 = false; - boolean foundEntity2 = false; - - if(association.getEntity1().getType().equals(query.getEntityMeta().getEntityType())) { - foundEntity1 = true; - } - - if(association.getEntity2().getType().equals(query.getEntityMeta().getEntityType())) { - foundEntity2 = true; - } - - // 結合情報で定義されているエンティティかチェックします。 - for(JoinCondition condition : query.getJoinConditions()) { - if(association.getEntity1().getType().equals(condition.getToEntity().getType())) { - foundEntity1 = true; - } - - if(association.getEntity2().getType().equals(condition.getToEntity().getType())) { - foundEntity2 = true; - } - - if(foundEntity1 && foundEntity2) { - continue; - } - - } - - if(!foundEntity1 || !foundEntity2) { - throw new IllegalOperateException(context.getMessageFormatter().create("query.noExistsTargetAssociateEntity") - .param("entity1", association.getEntity1().getType()) - .param("entity2", association.getEntity2().getType()) - .format()); - } - } - - - /** - * 抽出対象のエンティティやカラム情報を準備します。 - * {@link SelectClause}を準備します。 - */ - private void prepareTargetColumn() { - - if(counting) { - // 件数取得の場合 - String sql = context.getDialect().getCountSql(); - selectClause.addSql(sql); - - } else { - - // 抽出対象のプロパティが参照対象のテーブルに存在するかチェックする - validateTargetProperty(query.getIncludesProperties()); - validateTargetProperty(query.getExcludesProperties()); - - // 参照対象のプロパティと所属するエンティティのマップ - final Map> selectedPropertyMetaMap = new LinkedHashMap<>(); - - // ベースとなるエンティティのカラム指定の場合 - for(PropertyMeta propertyMeta : query.getEntityMeta().getAllColumnPropertyMeta()) { - - if(!isTargetProperty(propertyMeta)) { - // 抽出対象のプロパティでない場合はスキップします。 - continue; - } - - String tableAlias = tableNameResolver.getTableAlias(query.getEntityPath()); - selectClause.addSql(tableAlias, propertyMeta.getColumnMeta().getName()); - selectedPropertyMetaMap.put(propertyMeta, query.getBaseClass()); - - } - - // 結合しているエンティティの場合 - for(JoinCondition jc : query.getJoinConditions()) { - EntityPath joinEntity = jc.getToEntity(); - EntityMeta joinEntityMeta = query.getEntityMetaMap().get(joinEntity.getType()); - - for(PropertyMeta propertyMeta : joinEntityMeta.getAllColumnPropertyMeta()) { - final String propertyName = propertyMeta.getName(); - final PropertyPath propertyPath = joinEntity.getPropertyPath(propertyName); - - if(propertyMeta.isTransient()) { - continue; - } - - if(query.getExcludesProperties().contains(propertyPath)) { - continue; - } - - if(!query.getIncludesProperties().isEmpty() - && !query.getIncludesProperties().contains(propertyPath)) { - continue; - } - - String tableAlias = tableNameResolver.getTableAlias(joinEntity); - selectClause.addSql(tableAlias, propertyMeta.getColumnMeta().getName()); - selectedPropertyMetaMap.put(propertyMeta, joinEntity.getType()); - - } - } - - this.targetPropertyMetaEntityTypeMap = Collections.unmodifiableMap(selectedPropertyMetaMap); - - } - - } - - /** - * 対象のプロパティが参照対象のテーブルのエンティティに所属するかチェックします。 - * - * @param properties チェック対象のプロパティ一覧 - * @throws IllegalOperateException 既に同じ組み合わせのエンティティ(テーブル)を指定しているときにスローされます。 - */ - private void validateTargetProperty(final Collection> properties) { - - for(PropertyPath prop : properties) { - - // チェックしたエンティのクラスタイプ - Set> checkedClassTypes = new LinkedHashSet<>(); - - // 参照元のエンティティのチェック - EntityPath parentPath = (EntityPath)prop.getPathMeta().getParent(); - if(query.getEntityPath().equals(parentPath)) { - continue; - } - checkedClassTypes.add(query.getEntityPath().getType()); - - // 結合先のエンティティかチェック - boolean foundInJoinedEntity = false; - for(JoinCondition condition : query.getJoinConditions()) { - EntityPath joinEntityPath = condition.getToEntity(); - if(joinEntityPath.equals(parentPath)) { - foundInJoinedEntity = true; - continue; - } - checkedClassTypes.add(joinEntityPath.getType()); - } - - if(foundInJoinedEntity) { - Class[] classTypes = checkedClassTypes.toArray(new Class[checkedClassTypes.size()]); - throw new IllegalOperateException(context.getMessageFormatter().create("noAnyIncludeProperty") - .paramWithClass("classTypes", classTypes) - .param("entityClass", parentPath.getPathMeta().getType()) - .param("properyName", prop.getPathMeta().getElement()) - .format()); - } - - } - - } - - /** - * 抽出対象のプロパティか判定します。 - * @param propertyMeta プロパティ情報 - * @return 抽出対象のとき、{@literal true} を返します。 - */ - private boolean isTargetProperty(final PropertyMeta propertyMeta) { - - if(propertyMeta.isId()) { - return true; - } - - if(propertyMeta.isTransient()) { - return false; - } - - if(query.getIncludesProperties().isEmpty() && query.getExcludesProperties().isEmpty()) { - return true; - } - - final String propertyName = propertyMeta.getName(); - final PropertyPath propertyPath = query.getEntityPath().findPropertyPath(propertyName); - - if(query.getIncludesProperties().contains(propertyPath)) { - return true; - } - - if(query.getExcludesProperties().contains(propertyPath)) { - return false; - } - - // 抽出対象が指定されているときは、その他はすべて抽出対象外とする。 - return query.getIncludesProperties().isEmpty(); - - } - - /** - * 抽出対象のテーブルや結合対象のテーブルの準備を行います。 - * {@link FromClause}の準備を行います。 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private void prepareTargetTable() { - - // from句の指定 - fromClause.addSql(query.getEntityMeta().getTableMeta().getFullName(), tableNameResolver.getTableAlias(query.getEntityPath())); - - for(JoinCondition jc : query.getJoinConditions()) { - - // 結合対象のテーブル情報の取得 - EntityPath joinEntity = jc.getToEntity(); - EntityMeta joinEntityMeta = query.getEntityMetaMap().get(joinEntity.getType()); - String tableName = joinEntityMeta.getTableMeta().getFullName(); - String tableAlias = tableNameResolver.getTableAlias(joinEntity); - - Predicate where = jc.getConditioner().build(joinEntity); - - // テーブルの結合条件の評価 - MetamodelWhereVisitor visitor = new MetamodelWhereVisitor(query.getEntityMetaMap(), context.getDialect(), context.getEntityMetaFactory() - ,tableNameResolver); - visitor.visit(new MetamodelWhere(where)); - String condition = visitor.getCriteria(); - - // JOIN句の追加 - fromClause.addSql(jc.getType(), tableName, tableAlias, condition); - - // 結合条件にプレースホルダーがあるとき、パラメータの値を追加する - paramValues.addAll(visitor.getParamValues()); - - } - - } - - - - /** - * IDプロパティ及びバージョンを準備します。 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private void prepareIdVersion() { - - if(query.getIdPropertyValues() == null && query.getVersionPropertyValue() == null) { - // 主キーとバージョンキーの両方の指定がない場合はスキップする。 - return; - - } else if(query.getIdPropertyValues() == null && query.getVersionPropertyValue() != null) { - // 主キーが指定されず、バージョンだけ指定されている場合 - throw new IllegalOperateException(context.getMessageFormatter().create("query.emptyIdWithVersion") - .format()); - } - - final SimpleWhereBuilder where = new SimpleWhereBuilder(); - final String tableAliasName = tableNameResolver.getTableAlias(query.getEntityPath()); - - // IDの条件指定 - for(int i=0; i < query.getIdPropertyValues().length; i++) { - PropertyMeta propertyMeta = query.getEntityMeta().getIdPropertyMetaList().get(i); - String exp = String.format("%s.%s = ?", tableAliasName, propertyMeta.getColumnMeta().getName()); - - ValueType valueType = propertyMeta.getValueType(); - Object value = valueType.getSqlParameterValue(query.getIdPropertyValues()[i]); - - where.exp(exp, value); - } - - // バージョンの指定 - if(query.getVersionPropertyValue() != null) { - - PropertyMeta propertyMeta = query.getEntityMeta().getVersionPropertyMeta().get(); - String exp = String.format("%s.%s = ?", tableAliasName, propertyMeta.getColumnMeta().getName()); - - ValueType valueType = propertyMeta.getValueType(); - Object value = valueType.getSqlParameterValue(query.getVersionPropertyValue()); - - where.exp(exp, value); - } - - SimpleWhereVisitor visitor = new SimpleWhereVisitor(); - where.accept(visitor); - - this.whereClause.addSql(visitor.getCriteria()); - this.paramValues.addAll(visitor.getParamValues()); - - } - - /** - * 条件文の組み立て - */ - private void prepareCondition() { - - if(query.getWhere() == null) { - return; - } - - MetamodelWhereVisitor visitor = new MetamodelWhereVisitor(query.getEntityMetaMap(), context.getDialect(), context.getEntityMetaFactory(), - tableNameResolver); - visitor.visit(new MetamodelWhere(query.getWhere())); - - this.whereClause.addSql(visitor.getCriteria()); - this.paramValues.addAll(visitor.getParamValues()); - - } - - /** - * ORDER BY句の準備をします。 - */ - private void prepareOrderBy() { - - if (query.getOrders().isEmpty()) { - return; - } - - for(OrderSpecifier order : query.getOrders()) { - PathMeta pathMeta = order.getPath().getPathMeta(); - Path rootPath = pathMeta.findRootPath(); - String propertyName = pathMeta.getElement(); - Optional propertyMeta = query.getEntityMeta().findPropertyMeta(propertyName); - if(propertyMeta.isEmpty()) { - throw new IllegalQueryException("unknwon property : " + propertyName); - } - - String tableName = tableNameResolver.getTableAlias(rootPath); - String columnName; - if(tableName != null) { - columnName = tableName + "." + propertyMeta.get().getColumnMeta().getName(); - } else { - columnName = propertyMeta.get().getColumnMeta().getName();; - } - - orderByClause.addSql(columnName + " " + order.getOrder().name()); - } - - } - - /** - * FOR UPDATE句の準備をします。 - */ - private void prepareForUpdate() { - - if(query.getForUpdateType() == null) { - this.forUpdateClause = ""; - return; - } - - // LIMIT句を指定していないかのチェック - if(query.getLimit() > 0 || query.getOffset() >= 0) { - throw new IllegalOperateException(context.getMessageFormatter().create("query.notSupportPaginationWithForUpdate") - .format()); - } - - final Dialect dialect = context.getDialect(); - this.forUpdateClause = dialect.getForUpdateSql(query.getForUpdateType(), query.getForUpdateWaitSeconds()); - - } - - /** - * 実行するSQLの組み立て - */ - private void prepareSql() { - - final Dialect dialect = context.getDialect(); - - final String hintComment; - if(StringUtils.hasLength(query.getHint())) { - hintComment = dialect.getHintComment(query.getHint()); - } else { - hintComment = ""; - } - - String sql = "select " - + hintComment - + selectClause.toSql() - + fromClause.toSql() - + whereClause.toSql() - + orderByClause.toSql() - + forUpdateClause; - - if(query.getLimit() > 0 || query.getOffset() >= 0) { - sql = dialect.convertLimitSql(sql, query.getOffset(), query.getLimit()); - } - - this.executedSql = sql; - - } - - /** - * 件数カウントするクエリを実行します。 - * - * @return 件数カウント - */ - public long getCount() { - prepare(); - - return getJdbcTemplate().queryForObject(executedSql, Long.class, paramValues.toArray()); - } - - /** - * 1件だけヒットすることを前提として検索クエリを実行します。 - * - * @param callback エンティティマッピング後のコールバック処理 - * @return エンティティのベースオブジェクト。 - * @throws IncorrectResultSizeDataAccessException 1件も見つからない場合、2件以上見つかった場合にスローされます。 - */ - public T getSingleResult(EntityMappingCallback callback) { - prepare(); - - AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, - query.getJoinAssociations(), Optional.ofNullable(callback)); - return getJdbcTemplate().queryForObject(executedSql, rowMapper, paramValues.toArray()); - } - - /** - * 1件だけヒットすることを前提として検索クエリを実行します。 - * - * @param callback エンティティマッピング後のコールバック処理。 - * @return エンティティのベースオブジェクト。1件も対象がないときは空を返します。 - * @throws IncorrectResultSizeDataAccessException 2件以上見つかった場合にスローされます。 - */ - public Optional getOptionalResult(EntityMappingCallback callback) { - prepare(); - - AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, - query.getJoinAssociations(), Optional.ofNullable(callback)); - final List ret = getJdbcTemplate().query(executedSql, rowMapper, paramValues.toArray()); - if(ret.isEmpty()) { - return Optional.empty(); - } else if(ret.size() > 1) { - throw new IncorrectResultSizeDataAccessException(1, ret.size()); - } else { - return Optional.of(ret.get(0)); - } - } - - /** - * 検索クエリを実行します。 - * - * @param callback エンティティマッピング後のコールバック処理。 - * @return 検索してヒットした複数のベースオブジェクト。1件も対象がないときは空のリストを返します。 - */ - public List getResultList(EntityMappingCallback callback) { - prepare(); - - AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, - query.getJoinAssociations(), Optional.ofNullable(callback)); - return getJdbcTemplate().query(executedSql, rowMapper, paramValues.toArray()); - } - - /** - * 結果を {@link Stream} で返す検索クエリを実行します。 - * @param callback エンティティマッピング後のコールバック処理。 - * @return 問い合わせの結果 - */ - public Stream getResultStream(EntityMappingCallback callback) { - prepare(); - - AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, - query.getJoinAssociations(), Optional.ofNullable(callback)); - return getJdbcTemplate().queryForStream(executedSql, rowMapper, paramValues.toArray()); - } - - /** - * {@link JdbcTemplate}を取得します。 - * @return {@link JdbcTemplate}のインスタンス。 - */ - private JdbcTemplate getJdbcTemplate() { - return JdbcTemplateBuilder.create(context.getDataSource(), context.getJdbcTemplateProperties()) - .queryTimeout(query.getQueryTimeout()) - .fetchSize(query.getFetchSize()) - .maxRows(query.getMaxRows()) - .build(); - } - -} +package com.github.mygreen.sqlmapper.core.query.auto; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.util.StringUtils; + +import com.github.mygreen.sqlmapper.core.SqlMapperContext; +import com.github.mygreen.sqlmapper.core.dialect.Dialect; +import com.github.mygreen.sqlmapper.core.mapper.AutoEntityRowMapper; +import com.github.mygreen.sqlmapper.core.mapper.EntityMappingCallback; +import com.github.mygreen.sqlmapper.core.meta.EntityMeta; +import com.github.mygreen.sqlmapper.core.meta.PropertyMeta; +import com.github.mygreen.sqlmapper.core.query.FromClause; +import com.github.mygreen.sqlmapper.core.query.IllegalOperateException; +import com.github.mygreen.sqlmapper.core.query.IllegalQueryException; +import com.github.mygreen.sqlmapper.core.query.JdbcTemplateBuilder; +import com.github.mygreen.sqlmapper.core.query.JoinAssociation; +import com.github.mygreen.sqlmapper.core.query.JoinCondition; +import com.github.mygreen.sqlmapper.core.query.OrderByClause; +import com.github.mygreen.sqlmapper.core.query.SelectClause; +import com.github.mygreen.sqlmapper.core.query.TableNameResolver; +import com.github.mygreen.sqlmapper.core.query.WhereClause; +import com.github.mygreen.sqlmapper.core.type.ValueType; +import com.github.mygreen.sqlmapper.core.where.metamodel.MetamodelWhere; +import com.github.mygreen.sqlmapper.core.where.metamodel.MetamodelWhereVisitor; +import com.github.mygreen.sqlmapper.core.where.simple.SimpleWhereBuilder; +import com.github.mygreen.sqlmapper.core.where.simple.SimpleWhereVisitor; +import com.github.mygreen.sqlmapper.metamodel.EntityPath; +import com.github.mygreen.sqlmapper.metamodel.OrderSpecifier; +import com.github.mygreen.sqlmapper.metamodel.Path; +import com.github.mygreen.sqlmapper.metamodel.PathMeta; +import com.github.mygreen.sqlmapper.metamodel.Predicate; +import com.github.mygreen.sqlmapper.metamodel.PropertyPath; + + +/** + * 抽出を行うSQLを自動生成するクエリを実行します。 + * {@link AutoSelectImpl}のクエリ実行処理の移譲先です。 + * + * + * @author T.TSUCHIE + * + * @param 処理対象となるエンティティの型 + */ +public class AutoSelectExecutor { + + /** + * クエリ情報 + */ + private final AutoSelectImpl query; + + /** + * 設定情報 + */ + private final SqlMapperContext context; + + /** + * SELECT COUNT(*)~で行数を取得する場合にtrue + */ + private final boolean counting; + + /** + * select句です。 + */ + private SelectClause selectClause = new SelectClause(); + + /** + * from句です。 + */ + private FromClause fromClause = new FromClause(); + + /** + * where句です。 + */ + private final WhereClause whereClause = new WhereClause(); + + /** + * order by句です。 + */ + private final OrderByClause orderByClause = new OrderByClause(); + + /** + * テーブルの別名を管理します。 + */ + private final TableNameResolver tableNameResolver = new TableNameResolver(); + + /** + * for update句です。 + */ + private String forUpdateClause; + + /** + * 実行するSQLです + */ + private String executedSql; + + /** + * クエリのパラメータです。 + */ + private final List paramValues = new ArrayList<>(); + + /** + * 抽出対象のプロパティ情報とプロパティのマッピング先のエンティティのタイプ情報 + */ + private Map> targetPropertyMetaEntityTypeMap; + + /** + * インスタンスの作成 + * + * @param query クエリ情報 + * @param counting カウント用のクエリかどうか + */ + public AutoSelectExecutor(AutoSelectImpl query, boolean counting) { + this.query = query; + this.counting = counting; + this.context = query.getContext(); + } + + /** + * クエリ実行の準備を行います。 + */ + private void prepare() { + + prepareTableAlias(); + prepareTargetColumn(); + prepareTargetTable(); + prepareIdVersion(); + prepareCondition(); + prepareOrderBy(); + prepareForUpdate(); + + prepareSql(); + } + + /** + * テーブルの別名を準備します。 + */ + private void prepareTableAlias() { + + // FROM句指定のテーブル + tableNameResolver.prepareTableAlias(query.getEntityPath()); + + // JOINテーブルのエイリアス + for(JoinCondition condition : query.getJoinConditions()) { + tableNameResolver.prepareTableAlias(condition.getToEntity()); + } + + // 構成定義のバリデーション + for(JoinAssociation association : query.getJoinAssociations()) { + validateJoinAssociation(association); + } + } + + /** + * 構成定義が抽出対象のテーブルのエンティティかどうか + * + * @param association 構成定義情報 + */ + private void validateJoinAssociation(JoinAssociation association) { + + // 参照対象のエンティティかチェックする + boolean foundEntity1 = false; + boolean foundEntity2 = false; + + if(association.getEntity1().getType().equals(query.getEntityMeta().getEntityType())) { + foundEntity1 = true; + } + + if(association.getEntity2().getType().equals(query.getEntityMeta().getEntityType())) { + foundEntity2 = true; + } + + // 結合情報で定義されているエンティティかチェックします。 + for(JoinCondition condition : query.getJoinConditions()) { + if(association.getEntity1().getType().equals(condition.getToEntity().getType())) { + foundEntity1 = true; + } + + if(association.getEntity2().getType().equals(condition.getToEntity().getType())) { + foundEntity2 = true; + } + + if(foundEntity1 && foundEntity2) { + continue; + } + + } + + if(!foundEntity1 || !foundEntity2) { + throw new IllegalOperateException(context.getMessageFormatter().create("query.noExistsTargetAssociateEntity") + .param("entity1", association.getEntity1().getType()) + .param("entity2", association.getEntity2().getType()) + .format()); + } + } + + + /** + * 抽出対象のエンティティやカラム情報を準備します。 + * {@link SelectClause}を準備します。 + */ + private void prepareTargetColumn() { + + if(counting) { + // 件数取得の場合 + String sql = context.getDialect().getCountSql(); + selectClause.addSql(sql); + + } else { + + // 抽出対象のプロパティが参照対象のテーブルに存在するかチェックする + validateTargetProperty(query.getIncludesProperties()); + validateTargetProperty(query.getExcludesProperties()); + + // 参照対象のプロパティと所属するエンティティのマップ + final Map> selectedPropertyMetaMap = new LinkedHashMap<>(); + + // ベースとなるエンティティのカラム指定の場合 + for(PropertyMeta propertyMeta : query.getEntityMeta().getAllColumnPropertyMeta()) { + + if(!isTargetProperty(propertyMeta)) { + // 抽出対象のプロパティでない場合はスキップします。 + continue; + } + + String tableAlias = tableNameResolver.getTableAlias(query.getEntityPath()); + selectClause.addSql(tableAlias, propertyMeta.getColumnMeta().getName()); + selectedPropertyMetaMap.put(propertyMeta, query.getBaseClass()); + + } + + // 結合しているエンティティの場合 + for(JoinCondition jc : query.getJoinConditions()) { + EntityPath joinEntity = jc.getToEntity(); + EntityMeta joinEntityMeta = query.getEntityMetaMap().get(joinEntity.getType()); + + for(PropertyMeta propertyMeta : joinEntityMeta.getAllColumnPropertyMeta()) { + final String propertyName = propertyMeta.getName(); + final PropertyPath propertyPath = joinEntity.getPropertyPath(propertyName); + + if(propertyMeta.isTransient()) { + continue; + } + + if(query.getExcludesProperties().contains(propertyPath)) { + continue; + } + + if(!query.getIncludesProperties().isEmpty() + && !query.getIncludesProperties().contains(propertyPath)) { + continue; + } + + String tableAlias = tableNameResolver.getTableAlias(joinEntity); + selectClause.addSql(tableAlias, propertyMeta.getColumnMeta().getName()); + selectedPropertyMetaMap.put(propertyMeta, joinEntity.getType()); + + } + } + + this.targetPropertyMetaEntityTypeMap = Collections.unmodifiableMap(selectedPropertyMetaMap); + + } + + } + + /** + * 対象のプロパティが参照対象のテーブルのエンティティに所属するかチェックします。 + * + * @param properties チェック対象のプロパティ一覧 + * @throws IllegalOperateException 既に同じ組み合わせのエンティティ(テーブル)を指定しているときにスローされます。 + */ + private void validateTargetProperty(final Collection> properties) { + + for(PropertyPath prop : properties) { + + // チェックしたエンティのクラスタイプ + Set> checkedClassTypes = new LinkedHashSet<>(); + + // 参照元のエンティティのチェック + EntityPath parentPath = (EntityPath)prop.getPathMeta().getParent(); + if(query.getEntityPath().equals(parentPath)) { + continue; + } + checkedClassTypes.add(query.getEntityPath().getType()); + + // 結合先のエンティティかチェック + boolean foundInJoinedEntity = false; + for(JoinCondition condition : query.getJoinConditions()) { + EntityPath joinEntityPath = condition.getToEntity(); + if(joinEntityPath.equals(parentPath)) { + foundInJoinedEntity = true; + continue; + } + checkedClassTypes.add(joinEntityPath.getType()); + } + + if(foundInJoinedEntity) { + Class[] classTypes = checkedClassTypes.toArray(new Class[checkedClassTypes.size()]); + throw new IllegalOperateException(context.getMessageFormatter().create("noAnyIncludeProperty") + .paramWithClass("classTypes", classTypes) + .param("entityClass", parentPath.getPathMeta().getType()) + .param("properyName", prop.getPathMeta().getElement()) + .format()); + } + + } + + } + + /** + * 抽出対象のプロパティか判定します。 + * @param propertyMeta プロパティ情報 + * @return 抽出対象のとき、{@literal true} を返します。 + */ + private boolean isTargetProperty(final PropertyMeta propertyMeta) { + + if(propertyMeta.isId()) { + return true; + } + + if(propertyMeta.isTransient()) { + return false; + } + + if(query.getIncludesProperties().isEmpty() && query.getExcludesProperties().isEmpty()) { + return true; + } + + final String propertyName = propertyMeta.getName(); + final PropertyPath propertyPath = query.getEntityPath().findPropertyPath(propertyName); + + if(query.getIncludesProperties().contains(propertyPath)) { + return true; + } + + if(query.getExcludesProperties().contains(propertyPath)) { + return false; + } + + // 抽出対象が指定されているときは、その他はすべて抽出対象外とする。 + return query.getIncludesProperties().isEmpty(); + + } + + /** + * 抽出対象のテーブルや結合対象のテーブルの準備を行います。 + * {@link FromClause}の準備を行います。 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private void prepareTargetTable() { + + // from句の指定 + fromClause.addSql(query.getEntityMeta().getTableMeta().getFullName(), tableNameResolver.getTableAlias(query.getEntityPath())); + + for(JoinCondition jc : query.getJoinConditions()) { + + // 結合対象のテーブル情報の取得 + EntityPath joinEntity = jc.getToEntity(); + EntityMeta joinEntityMeta = query.getEntityMetaMap().get(joinEntity.getType()); + String tableName = joinEntityMeta.getTableMeta().getFullName(); + String tableAlias = tableNameResolver.getTableAlias(joinEntity); + + Predicate where = jc.getConditioner().build(joinEntity); + + // テーブルの結合条件の評価 + MetamodelWhereVisitor visitor = new MetamodelWhereVisitor(query.getEntityMetaMap(), context.getDialect(), context.getEntityMetaFactory() + ,tableNameResolver); + visitor.visit(new MetamodelWhere(where)); + String condition = visitor.getCriteria(); + + // JOIN句の追加 + fromClause.addSql(jc.getType(), tableName, tableAlias, condition); + + // 結合条件にプレースホルダーがあるとき、パラメータの値を追加する + paramValues.addAll(visitor.getParamValues()); + + } + + } + + + + /** + * IDプロパティ及びバージョンを準備します。 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private void prepareIdVersion() { + + if(query.getIdPropertyValues() == null && query.getVersionPropertyValue() == null) { + // 主キーとバージョンキーの両方の指定がない場合はスキップする。 + return; + + } else if(query.getIdPropertyValues() == null && query.getVersionPropertyValue() != null) { + // 主キーが指定されず、バージョンだけ指定されている場合 + throw new IllegalOperateException(context.getMessageFormatter().create("query.emptyIdWithVersion") + .format()); + } + + final SimpleWhereBuilder where = new SimpleWhereBuilder(); + final String tableAliasName = tableNameResolver.getTableAlias(query.getEntityPath()); + + // IDの条件指定 + for(int i=0; i < query.getIdPropertyValues().length; i++) { + PropertyMeta propertyMeta = query.getEntityMeta().getIdPropertyMetaList().get(i); + String exp = String.format("%s.%s = ?", tableAliasName, propertyMeta.getColumnMeta().getName()); + + ValueType valueType = propertyMeta.getValueType(); + Object value = valueType.getSqlParameterValue(query.getIdPropertyValues()[i]); + + where.exp(exp, value); + } + + // バージョンの指定 + if(query.getVersionPropertyValue() != null) { + + PropertyMeta propertyMeta = query.getEntityMeta().getVersionPropertyMeta().get(); + String exp = String.format("%s.%s = ?", tableAliasName, propertyMeta.getColumnMeta().getName()); + + ValueType valueType = propertyMeta.getValueType(); + Object value = valueType.getSqlParameterValue(query.getVersionPropertyValue()); + + where.exp(exp, value); + } + + SimpleWhereVisitor visitor = new SimpleWhereVisitor(); + where.accept(visitor); + + this.whereClause.addSql(visitor.getCriteria()); + this.paramValues.addAll(visitor.getParamValues()); + + } + + /** + * 条件文の組み立て + */ + private void prepareCondition() { + + if(query.getWhere() == null) { + return; + } + + MetamodelWhereVisitor visitor = new MetamodelWhereVisitor(query.getEntityMetaMap(), context.getDialect(), context.getEntityMetaFactory(), + tableNameResolver); + visitor.visit(new MetamodelWhere(query.getWhere())); + + this.whereClause.addSql(visitor.getCriteria()); + this.paramValues.addAll(visitor.getParamValues()); + + } + + /** + * ORDER BY句の準備をします。 + */ + private void prepareOrderBy() { + + if (query.getOrders().isEmpty()) { + return; + } + + for(OrderSpecifier order : query.getOrders()) { + PathMeta pathMeta = order.getPath().getPathMeta(); + Path rootPath = pathMeta.findRootPath(); + String propertyName = pathMeta.getElement(); + Optional propertyMeta = query.getEntityMeta().findPropertyMeta(propertyName); + if(propertyMeta.isEmpty()) { + throw new IllegalQueryException("unknwon property : " + propertyName); + } + + String tableName = tableNameResolver.getTableAlias(rootPath); + String columnName; + if(tableName != null) { + columnName = tableName + "." + propertyMeta.get().getColumnMeta().getName(); + } else { + columnName = propertyMeta.get().getColumnMeta().getName();; + } + + orderByClause.addSql(columnName + " " + order.getOrder().name()); + } + + } + + /** + * FOR UPDATE句の準備をします。 + */ + private void prepareForUpdate() { + + if(query.getForUpdateType() == null) { + this.forUpdateClause = ""; + return; + } + + // LIMIT句を指定していないかのチェック + if(query.getLimit() > 0 || query.getOffset() >= 0) { + throw new IllegalOperateException(context.getMessageFormatter().create("query.notSupportPaginationWithForUpdate") + .format()); + } + + final Dialect dialect = context.getDialect(); + this.forUpdateClause = dialect.getForUpdateSql(query.getForUpdateType(), query.getForUpdateWaitSeconds()); + + } + + /** + * 実行するSQLの組み立て + */ + private void prepareSql() { + + final Dialect dialect = context.getDialect(); + + final String hintComment; + if(StringUtils.hasLength(query.getHint())) { + hintComment = dialect.getHintComment(query.getHint()); + } else { + hintComment = ""; + } + + String sql = "select " + + hintComment + + selectClause.toSql() + + fromClause.toSql() + + whereClause.toSql() + + orderByClause.toSql() + + forUpdateClause; + + if(query.getLimit() > 0 || query.getOffset() >= 0) { + sql = dialect.convertLimitSql(sql, query.getOffset(), query.getLimit()); + } + + this.executedSql = sql; + + } + + /** + * 件数カウントするクエリを実行します。 + * + * @return 件数カウント + */ + public long getCount() { + prepare(); + context.getSqlLogger().out(executedSql, paramValues); + return getJdbcTemplate().queryForObject(executedSql, Long.class, paramValues.toArray()); + } + + /** + * 1件だけヒットすることを前提として検索クエリを実行します。 + * + * @param callback エンティティマッピング後のコールバック処理 + * @return エンティティのベースオブジェクト。 + * @throws IncorrectResultSizeDataAccessException 1件も見つからない場合、2件以上見つかった場合にスローされます。 + */ + public T getSingleResult(EntityMappingCallback callback) { + prepare(); + context.getSqlLogger().out(executedSql, paramValues); + + AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, + query.getJoinAssociations(), Optional.ofNullable(callback)); + return getJdbcTemplate().queryForObject(executedSql, rowMapper, paramValues.toArray()); + } + + /** + * 1件だけヒットすることを前提として検索クエリを実行します。 + * + * @param callback エンティティマッピング後のコールバック処理。 + * @return エンティティのベースオブジェクト。1件も対象がないときは空を返します。 + * @throws IncorrectResultSizeDataAccessException 2件以上見つかった場合にスローされます。 + */ + public Optional getOptionalResult(EntityMappingCallback callback) { + prepare(); + context.getSqlLogger().out(executedSql, paramValues); + + AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, + query.getJoinAssociations(), Optional.ofNullable(callback)); + final List ret = getJdbcTemplate().query(executedSql, rowMapper, paramValues.toArray()); + if(ret.isEmpty()) { + return Optional.empty(); + } else if(ret.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, ret.size()); + } else { + return Optional.of(ret.get(0)); + } + } + + /** + * 検索クエリを実行します。 + * + * @param callback エンティティマッピング後のコールバック処理。 + * @return 検索してヒットした複数のベースオブジェクト。1件も対象がないときは空のリストを返します。 + */ + public List getResultList(EntityMappingCallback callback) { + prepare(); + context.getSqlLogger().out(executedSql, paramValues); + + AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, + query.getJoinAssociations(), Optional.ofNullable(callback)); + return getJdbcTemplate().query(executedSql, rowMapper, paramValues.toArray()); + } + + /** + * 結果を {@link Stream} で返す検索クエリを実行します。 + * @param callback エンティティマッピング後のコールバック処理。 + * @return 問い合わせの結果 + */ + public Stream getResultStream(EntityMappingCallback callback) { + prepare(); + context.getSqlLogger().out(executedSql, paramValues); + + AutoEntityRowMapper rowMapper = new AutoEntityRowMapper(query.getBaseClass(), targetPropertyMetaEntityTypeMap, + query.getJoinAssociations(), Optional.ofNullable(callback)); + return getJdbcTemplate().queryForStream(executedSql, rowMapper, paramValues.toArray()); + } + + /** + * {@link JdbcTemplate}を取得します。 + * @return {@link JdbcTemplate}のインスタンス。 + */ + private JdbcTemplate getJdbcTemplate() { + return JdbcTemplateBuilder.create(context.getDataSource(), context.getJdbcTemplateProperties()) + .queryTimeout(query.getQueryTimeout()) + .fetchSize(query.getFetchSize()) + .maxRows(query.getMaxRows()) + .build(); + } + +} diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoUpdateExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoUpdateExecutor.java index 7053263ec..1e7a0339b 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoUpdateExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/auto/AutoUpdateExecutor.java @@ -239,6 +239,8 @@ public int execute() { return 0; } + context.getSqlLogger().out(executedSql, paramValues); + final int rows = getJdbcTemplate().update(executedSql, paramValues.toArray()); if(isOptimisticLock()) { validateRows(rows); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlCountImpl.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlCountImpl.java index 074fc585f..d3d00d5ea 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlCountImpl.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlCountImpl.java @@ -56,6 +56,9 @@ public SqlCountImpl queryTimeout(int seconds) { public long getCount() { ProcessResult result = template.process(parameter); String sql = context.getDialect().convertGetCountSql(result.getSql()); + + context.getSqlLogger().out(sql, result.getParameters()); + return getJdbcTemplate().queryForObject(sql, Long.class, result.getParameters().toArray()); } diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlSelectExecutor.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlSelectExecutor.java index 11edd1e96..1a3e4cd28 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlSelectExecutor.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlSelectExecutor.java @@ -88,6 +88,7 @@ private void prepareSql() { */ public T getSingleResult(EntityMappingCallback callback) { prepare(); + context.getSqlLogger().out(executedSql, paramValues); SqlEntityRowMapper rowMapper = new SqlEntityRowMapper(query.getEntityMeta(), Optional.ofNullable(callback)); return getJdbcTemplate().queryForObject(executedSql, rowMapper, paramValues); @@ -102,6 +103,7 @@ public T getSingleResult(EntityMappingCallback callback) { */ public Optional getOptionalResult(EntityMappingCallback callback) { prepare(); + context.getSqlLogger().out(executedSql, paramValues); SqlEntityRowMapper rowMapper = new SqlEntityRowMapper(query.getEntityMeta(), Optional.ofNullable(callback)); final List ret = getJdbcTemplate().query(executedSql, rowMapper, paramValues); @@ -122,6 +124,7 @@ public Optional getOptionalResult(EntityMappingCallback callback) { */ public List getResultList(EntityMappingCallback callback) { prepare(); + context.getSqlLogger().out(executedSql, paramValues); SqlEntityRowMapper rowMapper = new SqlEntityRowMapper(query.getEntityMeta(), Optional.ofNullable(callback)); return getJdbcTemplate().query(executedSql, rowMapper, paramValues); @@ -134,6 +137,7 @@ public List getResultList(EntityMappingCallback callback) { */ public Stream getResultStream(EntityMappingCallback callback) { prepare(); + context.getSqlLogger().out(executedSql, paramValues); SqlEntityRowMapper rowMapper = new SqlEntityRowMapper(query.getEntityMeta(), Optional.ofNullable(callback)); return getJdbcTemplate().queryForStream(executedSql, rowMapper, paramValues); diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlUpdateImpl.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlUpdateImpl.java index 30df254ff..cfcaf8a7f 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlUpdateImpl.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/query/sql/SqlUpdateImpl.java @@ -56,6 +56,7 @@ public SqlUpdateImpl queryTimeout(int seconds) { @Override public int execute() { ProcessResult result = template.process(parameter); + context.getSqlLogger().out(result.getSql(), result.getParameters()); return getJdbcTemplate().update(result.getSql(), result.getParameters().toArray()); } diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/sqlmapper.properties b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/sqlmapper.properties index 072da5af3..9c0297552 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/sqlmapper.properties +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/sqlmapper.properties @@ -21,3 +21,8 @@ sqlmapper.table-id-generator.initial-value=0 sqlmapper.sql-template.cache-mode=true sqlmapper.sql-template.encoding=UTF-8 +## SQLのログ出力設定 +sqlmapper.show-sql.enabled=false +sqlmapper.show-sql.log-level=DEBUG +sqlmapper.show-sql.bind-param.enabled=false +sqlmapper.show-sql.bind-param.log-level=DEBUG diff --git a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/util/QueryUtils.java b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/util/QueryUtils.java index a0e73c7c5..c2717ff75 100644 --- a/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/util/QueryUtils.java +++ b/sqlmapper-parent/sqlmapper-core/src/main/java/com/github/mygreen/sqlmapper/core/util/QueryUtils.java @@ -9,7 +9,7 @@ /** * クエリ組み立て時のヘルパークラス * - * + * @version 0.4 * @author T.TSUCHIE * */ @@ -138,4 +138,19 @@ public static String escapeLike(final String str, final char escape) { return sb.toString(); } + /** + * 配列が空かどうか判定します。 + * + * @since 0.4 + * @param array 判定対象の配列 + * @return 配列が {@literal null} または要素数が {@literal 0}のときに {@literal true} を返します。 + */ + public static boolean isEmpty(final Object[] array) { + if(array == null || array.length == 0) { + return true; + } + + return false; + } + } diff --git a/sqlmapper-parent/sqlmapper-core/src/test/java/com/github/mygreen/sqlmapper/core/test/config/H2TestConfig.java b/sqlmapper-parent/sqlmapper-core/src/test/java/com/github/mygreen/sqlmapper/core/test/config/H2TestConfig.java index 3ded5a1da..c1fcb84c0 100644 --- a/sqlmapper-parent/sqlmapper-core/src/test/java/com/github/mygreen/sqlmapper/core/test/config/H2TestConfig.java +++ b/sqlmapper-parent/sqlmapper-core/src/test/java/com/github/mygreen/sqlmapper/core/test/config/H2TestConfig.java @@ -7,6 +7,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.transaction.annotation.EnableTransactionManagement; +import com.github.mygreen.sqlmapper.core.config.ShowSqlProperties; import com.github.mygreen.sqlmapper.core.config.SqlMapperConfigurationSupport; import com.github.mygreen.sqlmapper.core.dialect.Dialect; import com.github.mygreen.sqlmapper.core.dialect.H2Dialect; @@ -37,4 +38,14 @@ public DataSource dataSource() { public Dialect dialect() { return new H2Dialect(); } + + @Override + public ShowSqlProperties showSqlProperties() { + + ShowSqlProperties prop = super.showSqlProperties(); + prop.setEnabled(true); + prop.getBindParam().setEnabled(true); + + return prop; + } } diff --git a/sqlmapper-parent/sqlmapper-core/src/test/resources/logback.xml b/sqlmapper-parent/sqlmapper-core/src/test/resources/logback.xml index 46998dabf..965b89495 100644 --- a/sqlmapper-parent/sqlmapper-core/src/test/resources/logback.xml +++ b/sqlmapper-parent/sqlmapper-core/src/test/resources/logback.xml @@ -12,6 +12,11 @@ + + + + + diff --git a/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperAutoConfiguration.java b/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperAutoConfiguration.java index 31c8f4261..cdab3444d 100644 --- a/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperAutoConfiguration.java +++ b/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperAutoConfiguration.java @@ -31,6 +31,7 @@ import com.github.mygreen.sqlmapper.core.SqlMapperContext; import com.github.mygreen.sqlmapper.core.audit.AuditingEntityListener; import com.github.mygreen.sqlmapper.core.config.JdbcTemplateProperties; +import com.github.mygreen.sqlmapper.core.config.ShowSqlProperties; import com.github.mygreen.sqlmapper.core.config.SqlTemplateProperties; import com.github.mygreen.sqlmapper.core.config.TableIdGeneratorProperties; import com.github.mygreen.sqlmapper.core.dialect.Dialect; @@ -46,6 +47,7 @@ import com.github.mygreen.sqlmapper.core.meta.StoredPropertyMetaFactory; import com.github.mygreen.sqlmapper.core.naming.DefaultNamingRule; import com.github.mygreen.sqlmapper.core.naming.NamingRule; +import com.github.mygreen.sqlmapper.core.query.SqlLogger; import com.github.mygreen.sqlmapper.core.type.ValueTypeRegistry; import lombok.extern.slf4j.Slf4j; @@ -53,7 +55,7 @@ /** * SqlMapperによるAuto-Configuration設定 * - * @version 0.3 + * @version 0.4 * @author T.TSUCHIE * */ @@ -107,6 +109,7 @@ public SqlMapperContext sqlMapperContext() { context.setDataSource(dataSource); context.setJdbcTemplateProperties(jdbcTemplateProperties()); context.setTransactionManager(transactionManager()); + context.setSqlLogger(sqlLogger()); return context; @@ -130,6 +133,12 @@ public TableIdGeneratorProperties tableIdGeneratorProperties() { return sqlMapperProperties.getTableIdGenerator(); } + @Bean + @ConditionalOnMissingBean + public ShowSqlProperties showSqlProperties() { + return sqlMapperProperties.getShowSql(); + } + @Bean @ConditionalOnMissingBean public EntityMetaFactory entityMetaFactory() { @@ -246,5 +255,10 @@ public AuditingEntityListener auditingEntityListener() { return new AuditingEntityListener(); } + @Bean + @ConditionalOnMissingBean + public SqlLogger sqlLogger() { + return new SqlLogger(showSqlProperties()); + } } diff --git a/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperProperties.java b/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperProperties.java index db2af57e1..6c0480ef6 100644 --- a/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperProperties.java +++ b/sqlmapper-parent/sqlmapper-spring-boot/sqlmapper-spring-boot-autoconfigure/src/main/java/com/github/mygreen/sqlmapper/boot/autoconfigure/SqlMapperProperties.java @@ -3,6 +3,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import com.github.mygreen.sqlmapper.core.config.JdbcTemplateProperties; +import com.github.mygreen.sqlmapper.core.config.ShowSqlProperties; import com.github.mygreen.sqlmapper.core.config.SqlTemplateProperties; import com.github.mygreen.sqlmapper.core.config.TableIdGeneratorProperties; @@ -39,4 +40,10 @@ public class SqlMapperProperties { * SQLテンプレートの設定値 */ private SqlTemplateProperties sqlTemplate; + + /** + * SQLのログ出力の設定値 + */ + private ShowSqlProperties showSql; + } diff --git a/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/application.yml b/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/application.yml index 5622615ee..c5cc429f0 100644 --- a/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/application.yml +++ b/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/application.yml @@ -9,3 +9,7 @@ sqlmapper: table-id-generator: allocation-size: 1 initial-value: 10 + show-sql: + enabled: true + bind-param: + enabled: true diff --git a/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/logback.xml b/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/logback.xml new file mode 100644 index 000000000..10e1d60b9 --- /dev/null +++ b/sqlmapper-sample/sqlmapper-sample-spring-boot/src/main/resources/logback.xml @@ -0,0 +1,24 @@ + + + + + .%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n + + + + DEBUG + + + + + + + + + + + + + \ No newline at end of file