diff --git a/pom.xml b/pom.xml index db32eb4..1c43308 100644 --- a/pom.xml +++ b/pom.xml @@ -48,31 +48,30 @@ 1.18.2-R0.1-SNAPSHOT provided - - com.io.yutian.pixelpaper - pixelpaper-api - 1.18.2 - org.spigotmc spigot 1.18.2 nms + provided com.mojang authlib 3.3.39 + provided com.mojang brigadier 1.0.18 + provided com.mojang datafixerupper 4.1.27 + provided org.apache.commons @@ -84,6 +83,13 @@ zstd-jni 1.5.6-3 + + com.zaxxer + HikariCP + 4.0.3 + true + compile + \ No newline at end of file diff --git a/src/main/java/com/io/yutian/aulib/sql/action/AbstractSQLAction.java b/src/main/java/com/io/yutian/aulib/sql/action/AbstractSQLAction.java new file mode 100644 index 0000000..2f281b5 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/action/AbstractSQLAction.java @@ -0,0 +1,102 @@ +package com.io.yutian.aulib.sql.action; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import com.io.yutian.aulib.sql.api.function.SQLHandler; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +public abstract class AbstractSQLAction implements SQLAction { + + protected final @NotNull String sqlContent; + private final @NotNull SQLManagerImpl sqlManager; + private final @NotNull UUID uuid; + private final long createNanoTime; + + public AbstractSQLAction(@NotNull SQLManagerImpl manager, @NotNull String sql) { + this(manager, sql, System.nanoTime()); + } + + public AbstractSQLAction(@NotNull SQLManagerImpl manager, @NotNull String sql, @NotNull UUID uuid) { + this(manager, sql, uuid, System.nanoTime()); + } + + public AbstractSQLAction(@NotNull SQLManagerImpl manager, @NotNull String sql, long createNanoTime) { + this(manager, sql, UUID.randomUUID(), createNanoTime); + } + + public AbstractSQLAction(@NotNull SQLManagerImpl manager, @NotNull String sql, + @NotNull UUID uuid, long createNanoTime) { + Objects.requireNonNull(manager); + Objects.requireNonNull(sql); + Objects.requireNonNull(uuid); + this.sqlManager = manager; + this.sqlContent = sql; + this.uuid = uuid; + this.createNanoTime = createNanoTime; + } + + + @Override + public @NotNull UUID getActionUUID() { + return this.uuid; + } + + @Override + public @NotNull String getShortID() { + return getActionUUID().toString().substring(0, 8); + } + + @Override + public long getCreateTime(TimeUnit unit) { + return unit.convert(createNanoTime, TimeUnit.NANOSECONDS); + } + + @Override + public @NotNull String getSQLContent() { + return this.sqlContent.trim(); + } + + @Override + public @NotNull SQLManagerImpl getManager() { + return this.sqlManager; + } + + protected void debugMessage(List params) { + if (getManager().isDebugMode()) { + try { + getManager().getDebugHandler().beforeExecute(this, params); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + } + + @Override + @SuppressWarnings("FutureReturnValueIgnored") + public void executeAsync(SQLHandler success, SQLExceptionHandler failure) { + getManager().getExecutorPool().submit(() -> { + try { + T returnedValue = execute(); + if (success != null) success.accept(returnedValue); + } catch (SQLException e) { + handleException(failure, e); + } + }); + } + + @Override + public @NotNull CompletableFuture executeFuture(@NotNull SQLFunction handler) { + CompletableFuture future = new CompletableFuture<>(); + executeAsync((t -> future.complete(handler.apply(t))), (e, q) -> future.completeExceptionally(e)); + return future; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLBatchUpdateActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLBatchUpdateActionImpl.java new file mode 100644 index 0000000..6b297fe --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLBatchUpdateActionImpl.java @@ -0,0 +1,92 @@ +package com.io.yutian.aulib.sql.action; + +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateBatchAction; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import com.io.yutian.aulib.sql.util.StatementUtil; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class PreparedSQLBatchUpdateActionImpl + extends AbstractSQLAction> + implements PreparedSQLUpdateBatchAction { + + boolean returnKeys = false; + @NotNull List allParams = new ArrayList<>(); + + protected final @NotNull Class numberClass; + + public PreparedSQLBatchUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull String sql) { + super(manager, sql); + this.numberClass = numberClass; + this.allParams = new ArrayList<>(); + } + + public PreparedSQLBatchUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull UUID uuid, @NotNull String sql) { + super(manager, sql, uuid); + this.numberClass = numberClass; + } + + @Override + public PreparedSQLBatchUpdateActionImpl setAllParams(Iterable allParams) { + List paramsList = new ArrayList<>(); + allParams.forEach(paramsList::add); + this.allParams = paramsList; + return this; + } + + @Override + public PreparedSQLBatchUpdateActionImpl addParamsBatch(Object... params) { + this.allParams.add(params); + return this; + } + + @Override + public PreparedSQLBatchUpdateActionImpl returnGeneratedKeys() { + this.returnKeys = true; + return this; + } + + @Override + public PreparedSQLBatchUpdateActionImpl returnGeneratedKeys(Class keyTypeClass) { + return new PreparedSQLBatchUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent()) + .setAllParams(allParams).returnGeneratedKeys(); + } + + @Override + public @NotNull List execute() throws SQLException { + debugMessage(allParams); + + try (Connection connection = getManager().getConnection()) { + try (PreparedStatement statement = StatementUtil.createPrepareStatementBatch( + connection, getSQLContent(), allParams, returnKeys + )) { + int[] executed = statement.executeBatch(); + + if (!returnKeys) { + return Arrays.stream(executed).mapToObj(numberClass::cast).collect(Collectors.toList()); + } else { + try (ResultSet resultSet = statement.getGeneratedKeys()) { + List generatedKeys = new ArrayList<>(); + while (resultSet.next()) { + generatedKeys.add(resultSet.getObject(1, numberClass)); + } + return generatedKeys; + } + } + } + + } + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLUpdateActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLUpdateActionImpl.java new file mode 100644 index 0000000..e1e8e4c --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/action/PreparedSQLUpdateActionImpl.java @@ -0,0 +1,94 @@ +package com.io.yutian.aulib.sql.action; + +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import com.io.yutian.aulib.sql.util.StatementUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class PreparedSQLUpdateActionImpl + extends SQLUpdateActionImpl + implements PreparedSQLUpdateAction { + + Object[] params; + + public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull String sql) { + this(manager, numberClass, sql, (Object[]) null); + } + + public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull String sql, @Nullable List params) { + this(manager, numberClass, sql, params == null ? null : params.toArray()); + } + + public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull String sql, @Nullable Object[] params) { + super(manager, numberClass, sql); + this.params = params; + } + + public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull UUID uuid, @NotNull String sql, + Object[] params) { + super(manager, numberClass, uuid, sql); + this.params = params; + } + + @Override + public PreparedSQLUpdateActionImpl setParams(Object... params) { + this.params = params; + return this; + } + + @Override + public PreparedSQLUpdateActionImpl setParams(@Nullable Iterable params) { + if (params == null) { + return setParams((Object[]) null); + } else { + List paramsList = new ArrayList<>(); + params.forEach(paramsList::add); + return setParams(paramsList.toArray()); + } + } + + @Override + public @NotNull T execute() throws SQLException { + debugMessage(Collections.singletonList(params)); + + try (Connection connection = getManager().getConnection()) { + + try (PreparedStatement statement = StatementUtil.createPrepareStatement( + connection, getSQLContent(), params, returnGeneratedKeys + )) { + + int changes = statement.executeUpdate(); + if (!returnGeneratedKeys) return numberClass.cast(changes); + else { + try (ResultSet resultSet = statement.getGeneratedKeys()) { + return resultSet.next() ? resultSet.getObject(1, numberClass) : numberClass.cast(0); + } + } + + } + } + + } + + @Override + public SQLUpdateAction returnGeneratedKey(Class keyTypeClass) { + PreparedSQLUpdateActionImpl newAction = new PreparedSQLUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent(), params); + newAction.returnGeneratedKey(); + return newAction; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateActionImpl.java new file mode 100644 index 0000000..b5a98b2 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateActionImpl.java @@ -0,0 +1,65 @@ +package com.io.yutian.aulib.sql.action; + +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.UUID; + +public class SQLUpdateActionImpl + extends AbstractSQLAction + implements SQLUpdateAction { + + protected final @NotNull Class numberClass; + + protected boolean returnGeneratedKeys = false; + + public SQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull String sql) { + super(manager, sql); + this.numberClass = numberClass; + } + + public SQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class numberClass, + @NotNull UUID uuid, @NotNull String sql) { + super(manager, sql, uuid); + this.numberClass = numberClass; + } + + @Override + public @NotNull T execute() throws SQLException { + debugMessage(new ArrayList<>()); + + try (Connection connection = getManager().getConnection()) { + try (Statement statement = connection.createStatement()) { + + if (!returnGeneratedKeys) { + return numberClass.cast(statement.executeUpdate(getSQLContent())); + } else { + statement.executeUpdate(getSQLContent(), Statement.RETURN_GENERATED_KEYS); + + try (ResultSet resultSet = statement.getGeneratedKeys()) { + return resultSet.next() ? resultSet.getObject(1, numberClass) : numberClass.cast(0); + } + } + } + } + } + + @Override + public SQLUpdateAction returnGeneratedKey() { + this.returnGeneratedKeys = true; + return this; + } + + @Override + public SQLUpdateAction returnGeneratedKey(Class keyTypeClass) { + return new SQLUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent()).returnGeneratedKey(); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateBatchActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateBatchActionImpl.java new file mode 100644 index 0000000..452ab8a --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/action/SQLUpdateBatchActionImpl.java @@ -0,0 +1,59 @@ +package com.io.yutian.aulib.sql.action; + +import com.io.yutian.aulib.sql.api.action.SQLUpdateBatchAction; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class SQLUpdateBatchActionImpl + extends AbstractSQLAction> + implements SQLUpdateBatchAction { + + protected final List sqlContents = new ArrayList<>(); + + public SQLUpdateBatchActionImpl(@NotNull SQLManagerImpl manager, @NotNull String sql) { + super(manager, sql); + this.sqlContents.add(sql); + } + + @Override + public @NotNull List getSQLContents() { + return this.sqlContents; + } + + @Override + public SQLUpdateBatchAction addBatch(@NotNull String sql) { + Objects.requireNonNull(sql, "sql could not be null"); + this.sqlContents.add(sql); + return this; + } + + @Override + public @NotNull List execute() throws SQLException { + debugMessage(new ArrayList<>()); + + try (Connection connection = getManager().getConnection()) { + + try (Statement statement = connection.createStatement()) { + + for (String content : this.sqlContents) { + statement.addBatch(content); + } + + int[] executed = statement.executeBatch(); + + return Arrays.stream(executed).boxed().collect(Collectors.toList()); + } + + } + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/SQLAction.java b/src/main/java/com/io/yutian/aulib/sql/api/SQLAction.java new file mode 100644 index 0000000..da35601 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/SQLAction.java @@ -0,0 +1,254 @@ +package com.io.yutian.aulib.sql.api; + +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import com.io.yutian.aulib.sql.api.function.SQLHandler; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * SQLAction 是用于承载SQL语句并进行处理、返回的基本类。 + * + *
    + *
  • 同步执行 {@link #execute()}, {@link #execute(SQLFunction, SQLExceptionHandler)} + *
    同步执行方法中有会抛出异常的方法与不抛出异常的方法, + *
    若选择不抛出异常,则返回值可能为空,需要特殊处理。
  • + * + *
  • 异步执行 {@link #executeAsync(SQLHandler, SQLExceptionHandler)} + *
    异步执行时将提供成功与异常两种处理方式 + *
    可自行选择是否对数据或异常进行处理 + *
    默认的异常处理器为 {@link #defaultExceptionHandler()} + *
    若有特殊需要,可通过{@link #setExceptionHandler(SQLExceptionHandler)} 方法修改默认的处理器
  • + *
+ * + * @param 需要返回的类型 + * @author CarmJos + * @since 0.0.1 + */ +public interface SQLAction { + + /** + * 得到该Action的UUID + * + * @return UUID + */ + @NotNull UUID getActionUUID(); + + /** + * 得到短八位格式的UUID + * + * @return UUID(8) + */ + @NotNull String getShortID(); + + /** + * 得到该Action的创建时间。 + *
注意,此处获得的时间非时间戳毫秒数,仅用于计算耗时。 + * + * @return 创建时间 (毫秒) + */ + default long getCreateTime() { + return getCreateTime(TimeUnit.MILLISECONDS); + } + + /** + * 得到该Action的创建时间 + *
注意,此处获得的时间非时间戳毫秒数,仅用于计算耗时。 + * + * @param unit 时间单位 + * @return 创建时间 + */ + long getCreateTime(TimeUnit unit); + + /** + * 得到该Action所要执行的源SQL语句 + * + * @return 源SQL语句 + */ + @NotNull String getSQLContent(); + + /** + * 得到该Action所要执行的源SQL语句列表。 + * + * @return 源SQL语句列表 + */ + default @NotNull List getSQLContents() { + return Collections.singletonList(getSQLContent()); + } + + /** + * 得到承载该Action的对应{@link SQLManager} + * + * @return {@link SQLManager} + */ + @NotNull SQLManager getManager(); + + /** + * 执行该Action对应的SQL语句 + * + * @return 指定数据类型 + * @throws SQLException 当SQL操作出现问题时抛出 + */ + @NotNull T execute() throws SQLException; + + + /** + * 执行语句并返回值 + * + * @param exceptionHandler 异常处理器 默认为 {@link #defaultExceptionHandler()} + * @return 指定类型数据 + */ + @Nullable + default T execute(@Nullable SQLExceptionHandler exceptionHandler) { + return execute(t -> t, exceptionHandler); + } + + /** + * 执行语句并处理返回值 + * + * @param function 处理方法 + * @param exceptionHandler 异常处理器 默认为 {@link #defaultExceptionHandler()} + * @param 需要返回的内容 + * @return 指定类型数据 + */ + @Nullable + default R execute(@NotNull SQLFunction function, + @Nullable SQLExceptionHandler exceptionHandler) { + return execute(function, null, exceptionHandler); + } + + /** + * 执行语句并处理返回值 + * + * @param function 处理方法 + * @param defaultResult 默认结果,若处理后的结果为null,则返回该值 + * @param exceptionHandler 异常处理器 默认为 {@link #defaultExceptionHandler()} + * @param 需要返回的内容 + * @return 指定类型数据 + */ + @Nullable + @Contract("_,!null,_ -> !null") + default R execute(@NotNull SQLFunction function, + @Nullable R defaultResult, + @Nullable SQLExceptionHandler exceptionHandler) { + try { + return executeFunction(function, defaultResult); + } catch (SQLException exception) { + handleException(exceptionHandler, exception); + return null; + } + } + + /** + * 执行语句并处理返回值 + * + * @param function 处理方法 + * @param 需要返回的内容 + * @return 指定类型数据 + * @throws SQLException 当SQL操作出现问题时抛出 + */ + @Nullable + default R executeFunction(@NotNull SQLFunction<@NotNull T, R> function) throws SQLException { + return executeFunction(function, null); + } + + /** + * 执行语句并处理返回值 + * + * @param function 处理方法 + * @param defaultResult 默认结果,若处理后的结果为null,则返回该值 + * @param 需要返回的内容 + * @return 指定类型数据 + * @throws SQLException 当SQL操作出现问题时抛出 + */ + @Nullable + @Contract("_,!null -> !null") + default R executeFunction(@NotNull SQLFunction<@NotNull T, R> function, + @Nullable R defaultResult) throws SQLException { + try { + R result = function.apply(execute()); + return result == null ? defaultResult : result; + } catch (SQLException exception) { + throw new SQLException(exception); + } + } + + /** + * 异步执行SQL语句,采用默认异常处理,无需返回值。 + */ + default void executeAsync() { + executeAsync(null); + } + + /** + * 异步执行SQL语句 + * + * @param success 成功时的操作 + */ + default void executeAsync(@Nullable SQLHandler success) { + executeAsync(success, null); + } + + /** + * 异步执行SQL语句 + * + * @param success 成功时的操作 + * @param failure 异常处理器 默认为 {@link SQLAction#defaultExceptionHandler()} + */ + void executeAsync(@Nullable SQLHandler success, + @Nullable SQLExceptionHandler failure); + + /** + * 以异步Future方式执行SQL语句。 + * + * @return 异步执行的Future实例,可通过 {@link Future#get()} 阻塞并等待结果。 + */ + default @NotNull CompletableFuture executeFuture() { + return executeFuture((t -> null)); + } + + /** + * 以异步Future方式执行SQL语句。 + * + * @return 异步执行的Future实例,可通过 {@link Future#get()} 阻塞并等待结果。 + */ + @NotNull CompletableFuture executeFuture(@NotNull SQLFunction handler); + + default void handleException(@Nullable SQLExceptionHandler handler, SQLException exception) { + if (handler == null) handler = defaultExceptionHandler(); + handler.accept(exception, this); + } + + /** + * 获取管理器提供的默认异常处理器。 + * 若未使用过 {@link #setExceptionHandler(SQLExceptionHandler)} 方法, + * 则默认返回 {@link SQLExceptionHandler#detailed(Logger)} 。 + * + * @return {@link SQLExceptionHandler} + */ + default SQLExceptionHandler defaultExceptionHandler() { + return getManager().getExceptionHandler(); + } + + /** + * 设定通用的异常处理器。 + *
在使用 {@link #execute(SQLExceptionHandler)} 等相关方法时,若传入的处理器为null,则会采用此处理器。 + *
若该方法传入参数为 null,则会使用 {@link #defaultExceptionHandler()} 。 + * + * @param handler 异常处理器 + */ + default void setExceptionHandler(@Nullable SQLExceptionHandler handler) { + getManager().setExceptionHandler(handler); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/SQLBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/SQLBuilder.java new file mode 100644 index 0000000..3bf3079 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/SQLBuilder.java @@ -0,0 +1,31 @@ +package com.io.yutian.aulib.sql.api; + +import org.jetbrains.annotations.NotNull; + +/** + * SQLBuilder 是用于构建SQL语句以生成SQLAction执行操作的中间类。 + *
其连接了{@link SQLManager} 与 {@link SQLAction} ,避免大量的代码堆积 + *
也是本接口的核心功能所在 + * + * @author CarmJos + */ +public interface SQLBuilder { + + static @NotNull String withBackQuote(@NotNull String str) { + str = str.trim(); + return !str.isEmpty() && str.charAt(0) == '`' && str.charAt(str.length() - 1) == '`' ? str : "`" + str + "`"; + } + + static @NotNull String withQuote(@NotNull String str) { + str = str.trim(); + return !str.isEmpty() && str.charAt(0) == '\'' && str.charAt(str.length() - 1) == '\'' ? str : "'" + str + "'"; + } + + /** + * 得到承载该Builder的对应{@link SQLManager} + * + * @return {@link SQLManager} + */ + @NotNull SQLManager getManager(); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/SQLManager.java b/src/main/java/com/io/yutian/aulib/sql/api/SQLManager.java new file mode 100644 index 0000000..4d581ab --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/SQLManager.java @@ -0,0 +1,316 @@ +package com.io.yutian.aulib.sql.api; + +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateBatchAction; +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.SQLUpdateBatchAction; +import com.io.yutian.aulib.sql.api.builder.*; +import com.io.yutian.aulib.sql.api.function.SQLBiFunction; +import com.io.yutian.aulib.sql.api.function.SQLDebugHandler; +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +/** + * SQLManager 是EasySQL的核心类,用于管理数据库连接,提供数据库操作的方法。 + * + * @author CarmJos + */ +public interface SQLManager { + + Logger getLogger(); + + boolean isDebugMode(); + + + /** + * 获取用于执行 {@link SQLAction#executeAsync()} 的线程池。 + *
默认线程池为 {@link #defaultExecutorPool(String)} 。 + * + * @return {@link ExecutorService} + */ + @NotNull ExecutorService getExecutorPool(); + + /** + * 设定用于执行 {@link SQLAction#executeAsync()} 的线程池. + *
默认线程池为 {@link #defaultExecutorPool(String)} 。 + * + * @param executorPool {@link ExecutorService} + */ + void setExecutorPool(@NotNull ExecutorService executorPool); + + static ExecutorService defaultExecutorPool(String threadName) { + return Executors.newFixedThreadPool(4, r -> { + Thread thread = new Thread(r, threadName); + thread.setDaemon(true); + return thread; + }); + } + + + /** + * 设定是否启用调试模式。 + * 启用调试模式后,会在每次执行SQL语句时,调用 {@link #getDebugHandler()} 来输出调试信息。 + * + * @param debugMode 是否启用调试模式 + */ + void setDebugMode(@NotNull Supplier<@NotNull Boolean> debugMode); + + /** + * 设定是否启用调试模式。 + * 启用调试模式后,会在每次执行SQL语句时,调用 {@link #getDebugHandler()} 来输出调试信息。 + * + * @param enable 是否启用调试模式 + */ + default void setDebugMode(boolean enable) { + setDebugMode(() -> enable); + } + + /** + * 获取调试处理器,用于处理调试信息。 + * + * @return {@link SQLDebugHandler} + */ + @NotNull SQLDebugHandler getDebugHandler(); + + /** + * 设定调试处理器,默认为 {@link SQLDebugHandler#defaultHandler(Logger)} 。 + * + * @param debugHandler {@link SQLDebugHandler} + */ + void setDebugHandler(@NotNull SQLDebugHandler debugHandler); + + /** + * 得到连接池源 + * + * @return DataSource + */ + @NotNull DataSource getDataSource(); + + /** + * 得到一个数据库连接实例 + * + * @return Connection + * @throws SQLException 见 {@link DataSource#getConnection()} + */ + @NotNull Connection getConnection() throws SQLException; + + /** + * 得到正使用的查询。 + * + * @return 查询列表 + */ + @NotNull Map getActiveQuery(); + + /** + * 获取改管理器提供的默认异常处理器。 + * 若未使用过 {@link #setExceptionHandler(SQLExceptionHandler)} 方法, + * 则默认返回 {@link SQLExceptionHandler#detailed(Logger)} 。 + * + * @return {@link SQLExceptionHandler} + */ + @NotNull SQLExceptionHandler getExceptionHandler(); + + /** + * 设定通用的异常处理器。 + *
在使用 {@link SQLAction#execute(SQLExceptionHandler)} 等相关方法时,若传入的处理器为null,则会采用此处理器。 + *
若该方法传入参数为 null,则会使用 {@link SQLExceptionHandler#detailed(Logger)} 。 + * + * @param handler 异常处理器 + */ + void setExceptionHandler(@Nullable SQLExceptionHandler handler); + + /** + * 执行一条不需要返回结果的SQL语句(多用于UPDATE、REPLACE、DELETE方法) + * 该方法使用 Statement 实现,请注意SQL注入风险! + * + * @param sql SQL语句内容 + * @return 更新的行数 + * @see SQLUpdateAction + */ + @Nullable Integer executeSQL(String sql); + + /** + * 执行一条不需要返回结果的预处理SQL更改(UPDATE、REPLACE、DELETE) + * + * @param sql SQL语句内容 + * @param params SQL语句中 ? 的对应参数 + * @return 更新的行数 + * @see PreparedSQLUpdateAction + */ + @Nullable Integer executeSQL(String sql, Object[] params); + + /** + * 执行多条不需要返回结果的SQL更改(UPDATE、REPLACE、DELETE) + * + * @param sql SQL语句内容 + * @param paramsBatch SQL语句中对应?的参数组 + * @return 对应参数返回的行数 + * @see PreparedSQLUpdateBatchAction + */ + @Nullable List executeSQLBatch(String sql, Iterable paramsBatch); + + + /** + * 执行多条不需要返回结果的SQL。 + * 该方法使用 Statement 实现,请注意SQL注入风险! + * + * @param sql SQL语句内容 + * @param moreSQL 更多SQL语句内容 + * @return 对应参数返回的行数 + * @see SQLUpdateBatchAction + */ + @Nullable List executeSQLBatch(@NotNull String sql, String... moreSQL); + + /** + * 执行多条不需要返回结果的SQL。 + * + * @param sqlBatch SQL语句内容 + * @return 对应参数返回的行数 + */ + @Nullable List executeSQLBatch(@NotNull Iterable sqlBatch); + + /** + * 获取并操作 {@link DatabaseMetaData} 以得到需要的数据库消息。 + * + * @param reader 操作与读取的方法 + * @param 最终结果的返回类型 + * @return 最终结果,通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回。 + */ + default CompletableFuture fetchMetadata(@NotNull SQLFunction reader) { + return fetchMetadata((meta, conn) -> reader.apply(meta)); + } + + /** + * 获取并操作 {@link DatabaseMetaData} 提供的指定 {@link ResultSet} 以得到需要的数据库消息。 + *
该方法会自动关闭 {@link ResultSet} 。 + * + * @param supplier 操作 {@link DatabaseMetaData} 以提供信息所在的 {@link ResultSet} + * @param reader 读取 {@link ResultSet} 中指定信息的方法 + * @param 最终结果的返回类型 + * @return 最终结果,通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回。 + * @throws NullPointerException 当 supplier 提供的 {@link ResultSet} 为NULL时抛出 + */ + default CompletableFuture fetchMetadata(@NotNull SQLFunction supplier, + @NotNull SQLFunction<@NotNull ResultSet, R> reader) { + return fetchMetadata((meta, conn) -> supplier.apply(meta), reader); + } + + /** + * 获取并操作 {@link DatabaseMetaData} 以得到需要的数据库消息。 + * + * @param reader 操作与读取的方法 + * @param 最终结果的返回类型 + * @return 最终结果,通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回。 + */ + CompletableFuture fetchMetadata(@NotNull SQLBiFunction reader); + + /** + * 获取并操作 {@link DatabaseMetaData} 提供的指定 {@link ResultSet} 以得到需要的数据库消息。 + *
该方法会自动关闭 {@link ResultSet} 。 + * + * @param supplier 操作 {@link DatabaseMetaData} 以提供信息所在的 {@link ResultSet} + * @param reader 读取 {@link ResultSet} 中指定信息的方法 + * @param 最终结果的返回类型 + * @return 最终结果,通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回。 + * @throws NullPointerException 当 supplier 提供的 {@link ResultSet} 为NULL时抛出 + */ + CompletableFuture fetchMetadata(@NotNull SQLBiFunction supplier, + @NotNull SQLFunction<@NotNull ResultSet, R> reader); + + /** + * 在库中创建一个表。 + * + * @param tableName 表名 + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder createTable(@NotNull String tableName); + + /** + * 对库中的某个表执行更改。 + * + * @param tableName 表名 + * @return {@link TableAlterBuilder} + */ + TableAlterBuilder alterTable(@NotNull String tableName); + + /** + * 快速获取表的部分元数据。 + *
当需要获取其他元数据时,请使用 {@link #fetchMetadata(SQLFunction, SQLFunction)} 方法。 + * + * @param tablePattern 表名通配符 + * @return {@link TableMetadataBuilder} + */ + TableMetadataBuilder fetchTableMetadata(@NotNull String tablePattern); + + /** + * 新建一个查询。 + * + * @return {@link QueryBuilder} + */ + QueryBuilder createQuery(); + + /** + * 创建一条插入操作。 + * + * @param tableName 目标表名 + * @return {@link InsertBuilder} + */ + InsertBuilder> createInsert(@NotNull String tableName); + + /** + * 创建支持多组数据的插入操作。 + * + * @param tableName 目标表名 + * @return {@link InsertBuilder} + */ + InsertBuilder> createInsertBatch(@NotNull String tableName); + + /** + * 创建一条替换操作。 + * + * @param tableName 目标表名 + * @return {@link ReplaceBuilder} + */ + ReplaceBuilder> createReplace(@NotNull String tableName); + + /** + * 创建支持多组数据的替换操作。 + * + * @param tableName 目标表名 + * @return {@link ReplaceBuilder} + */ + ReplaceBuilder> createReplaceBatch(@NotNull String tableName); + + /** + * 创建更新操作。 + * + * @param tableName 目标表名 + * @return {@link UpdateBuilder} + */ + UpdateBuilder createUpdate(@NotNull String tableName); + + /** + * 创建删除操作。 + * + * @param tableName 目标表名 + * @return {@link DeleteBuilder} + */ + DeleteBuilder createDelete(@NotNull String tableName); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/SQLQuery.java b/src/main/java/com/io/yutian/aulib/sql/api/SQLQuery.java new file mode 100644 index 0000000..3d3002d --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/SQLQuery.java @@ -0,0 +1,75 @@ +package com.io.yutian.aulib.sql.api; + +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import com.io.yutian.aulib.sql.api.action.query.QueryAction; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +/** + * SQLQuery 是一个查询中间接口,用于查询操作的封装。 + * + * @author CarmJos + */ +public interface SQLQuery extends AutoCloseable { + + /** + * 获取该查询创建的时间 + *
注意,此处获得的时间非时间戳毫秒数,仅用于计算耗时。 + * + * @return 创建时间 + */ + default long getExecuteTime() { + return getExecuteTime(TimeUnit.MILLISECONDS); + } + + /** + * 获取该查询创建的时间 + *
注意,此处获得的时间非时间戳毫秒数,仅用于计算耗时。 + * + * @param timeUnit 时间单位 + * @return 创建时间 + */ + long getExecuteTime(TimeUnit timeUnit); + + /** + * 得到承载该SQLQuery的对应{@link SQLManager} + * + * @return {@link SQLManager} + */ + SQLManager getManager(); + + /** + * 得到承载该SQLQuery的对应{@link QueryAction} + * + * @return {@link QueryAction} 或 {@link PreparedQueryAction} + */ + QueryAction getAction(); + + ResultSet getResultSet(); + + default boolean containsResult(String columnName) throws SQLException { + return getResultSet() != null && getResultSet().getObject(columnName) != null; + } + + /** + * 得到设定的SQL语句 + * + * @return SQL语句 + */ + String getSQLContent(); + + /** + * 关闭所有内容 + */ + @Override + void close(); + + Statement getStatement(); + + Connection getConnection(); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/SQLTable.java b/src/main/java/com/io/yutian/aulib/sql/api/SQLTable.java new file mode 100644 index 0000000..c601c18 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/SQLTable.java @@ -0,0 +1,149 @@ +package com.io.yutian.aulib.sql.api; + +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateBatchAction; +import com.io.yutian.aulib.sql.api.builder.*; +import com.io.yutian.aulib.sql.api.function.SQLHandler; +import com.io.yutian.aulib.sql.api.table.NamedSQLTable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; +import java.util.Optional; + +/** + * SQLTable 基于 {@link TableCreateBuilder} 构建表,用于快速创建与该表相关的操作。 + *
    + *
  • 1. 调用 {@link NamedSQLTable#of(String, String[])} 方法创建一个 SQLTable 对象;
  • + *
  • 2. 在应用初始化阶段调用 {@link NamedSQLTable#create(SQLManager)} 方法初始化 SQLTable 对象;
  • + *
  • 3. 获取已创建的{@link NamedSQLTable} 实例,直接调用对应方法进行关于表的相关操作。
  • + *
+ * + * @author CarmJos + * @since 0.3.10 + */ +public interface SQLTable { + + static @NotNull NamedSQLTable of(@NotNull String tableName, @Nullable SQLHandler table) { + return new NamedSQLTable(tableName) { + @Override + public boolean create(@NotNull SQLManager sqlManager, String tablePrefix) throws SQLException { + if (this.manager == null) this.manager = sqlManager; + this.tablePrefix = tablePrefix; + + TableCreateBuilder tableBuilder = sqlManager.createTable(getTableName()); + if (table != null) table.accept(tableBuilder); + return tableBuilder.build().executeFunction(l -> l > 0, false); + } + }; + } + + static @NotNull NamedSQLTable of(@NotNull String tableName, @NotNull String[] columns) { + return of(tableName, columns, null); + } + + static @NotNull NamedSQLTable of(@NotNull String tableName, + @NotNull String[] columns, @Nullable String tableSettings) { + return of(tableName, builder -> { + builder.setColumns(columns); + if (tableSettings != null) builder.setTableSettings(tableSettings); + }); + } + + /** + * 以指定的 {@link SQLManager} 实例初始化并创建该表 + * + * @param sqlManager {@link SQLManager} 实例 + * @return 是否新创建了本表 (若已创建或创建失败则返回false) + * @throws SQLException 当数据库返回异常时抛出 + */ + boolean create(SQLManager sqlManager) throws SQLException; + + /** + * 得到 {@link #create(SQLManager)} 用于初始化本实例的 {@link SQLManager} 实例 + * + * @return {@link SQLManager} 实例 + */ + @Nullable SQLManager getSQLManager(); + + /** + * 得到本表表名,不得为空。 + * + * @return 本表表名 + */ + @NotNull String getTableName(); + + default @NotNull TableQueryBuilder createQuery() { + return Optional.ofNullable(getSQLManager()).map(this::createQuery) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull TableQueryBuilder createQuery(@NotNull SQLManager sqlManager) { + return sqlManager.createQuery().inTable(getTableName()); + } + + default @NotNull DeleteBuilder createDelete() { + return Optional.ofNullable(getSQLManager()).map(this::createDelete) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull DeleteBuilder createDelete(@NotNull SQLManager sqlManager) { + return sqlManager.createDelete(getTableName()); + } + + default @NotNull UpdateBuilder createUpdate() { + return Optional.ofNullable(getSQLManager()).map(this::createUpdate) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull UpdateBuilder createUpdate(@NotNull SQLManager sqlManager) { + return sqlManager.createUpdate(getTableName()); + } + + default @NotNull InsertBuilder> createInsert() { + return Optional.ofNullable(getSQLManager()).map(this::createInsert) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull InsertBuilder> createInsert(@NotNull SQLManager sqlManager) { + return sqlManager.createInsert(getTableName()); + } + + default @NotNull InsertBuilder> createInsertBatch() { + return Optional.ofNullable(getSQLManager()).map(this::createInsertBatch) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull InsertBuilder> createInsertBatch(@NotNull SQLManager sqlManager) { + return sqlManager.createInsertBatch(getTableName()); + } + + default @NotNull ReplaceBuilder> createReplace() { + return Optional.ofNullable(getSQLManager()).map(this::createReplace) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + + } + + default @NotNull ReplaceBuilder> createReplace(@NotNull SQLManager sqlManager) { + return sqlManager.createReplace(getTableName()); + } + + default @NotNull ReplaceBuilder> createReplaceBatch() { + return Optional.ofNullable(getSQLManager()).map(this::createReplaceBatch) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull ReplaceBuilder> createReplaceBatch(@NotNull SQLManager sqlManager) { + return sqlManager.createReplaceBatch(getTableName()); + } + + default @NotNull TableAlterBuilder alter() { + return Optional.ofNullable(getSQLManager()).map(this::alter) + .orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger.")); + } + + default @NotNull TableAlterBuilder alter(@NotNull SQLManager sqlManager) { + return sqlManager.alterTable(getTableName()); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateAction.java new file mode 100644 index 0000000..1c5d327 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateAction.java @@ -0,0 +1,24 @@ +package com.io.yutian.aulib.sql.api.action; + +import org.jetbrains.annotations.Nullable; + +public interface PreparedSQLUpdateAction extends SQLUpdateAction { + + /** + * 设定SQL语句中所有 ? 对应的参数 + * + * @param params 参数内容 + * @return {@link PreparedSQLUpdateAction} + */ + PreparedSQLUpdateAction setParams(Object... params); + + /** + * 设定SQL语句中所有 ? 对应的参数 + * + * @param params 参数内容 + * @return {@link PreparedSQLUpdateAction} + * @since 0.4.0 + */ + PreparedSQLUpdateAction setParams(@Nullable Iterable params); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateBatchAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateBatchAction.java new file mode 100644 index 0000000..4dab1c7 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/PreparedSQLUpdateBatchAction.java @@ -0,0 +1,42 @@ +package com.io.yutian.aulib.sql.api.action; + +import com.io.yutian.aulib.sql.api.SQLAction; + +import java.util.List; + +public interface PreparedSQLUpdateBatchAction extends SQLAction> { + + /** + * 设定多组SQL语句中所有 ? 对应的参数 + * + * @param allParams 所有参数内容 + * @return {@link PreparedSQLUpdateBatchAction} + */ + PreparedSQLUpdateBatchAction setAllParams(Iterable allParams); + + /** + * 添加一组SQL语句中所有 ? 对应的参数 + * + * @param params 参数内容 + * @return {@link PreparedSQLUpdateBatchAction} + */ + PreparedSQLUpdateBatchAction addParamsBatch(Object... params); + + /** + * 设定该操作返回自增键序列。 + * + * @return {@link SQLUpdateAction} + */ + PreparedSQLUpdateBatchAction returnGeneratedKeys(); + + /** + * 设定该操作返回自增键序列。 + * + * @param keyTypeClass 自增序列的数字类型 + * @param 自增键序列类型 {@link Number} + * @return {@link SQLUpdateAction} + * @since 0.4.0 + */ + PreparedSQLUpdateBatchAction returnGeneratedKeys(Class keyTypeClass); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateAction.java new file mode 100644 index 0000000..cb90f08 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateAction.java @@ -0,0 +1,26 @@ +package com.io.yutian.aulib.sql.api.action; + +import com.io.yutian.aulib.sql.api.SQLAction; + +public interface SQLUpdateAction extends SQLAction { + + + /** + * 设定该操作返回自增键序列。 + * + * @return {@link SQLUpdateAction} + */ + SQLUpdateAction returnGeneratedKey(); + + /** + * 设定该操作返回自增键序列。 + * + * @param keyTypeClass 自增序列的数字类型 + * @param 自增键序列类型 {@link Number} + * @return {@link SQLUpdateAction} + * @since 0.4.0 + */ + SQLUpdateAction returnGeneratedKey(Class keyTypeClass); + + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateBatchAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateBatchAction.java new file mode 100644 index 0000000..743e708 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/SQLUpdateBatchAction.java @@ -0,0 +1,27 @@ +package com.io.yutian.aulib.sql.api.action; + +import com.io.yutian.aulib.sql.api.SQLAction; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@SuppressWarnings("UnusedReturnValue") +public interface SQLUpdateBatchAction extends SQLAction> { + + /** + * 添加一条批量执行的SQL语句 + * + * @param sql SQL语句 + * @return {@link SQLUpdateBatchAction} + */ + SQLUpdateBatchAction addBatch(@NotNull String sql); + + @Override + default @NotNull String getSQLContent() { + return getSQLContents().get(0); + } + + @Override + @NotNull List getSQLContents(); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/query/PreparedQueryAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/query/PreparedQueryAction.java new file mode 100644 index 0000000..ecb1033 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/query/PreparedQueryAction.java @@ -0,0 +1,35 @@ +package com.io.yutian.aulib.sql.api.action.query; + +import org.jetbrains.annotations.Nullable; + +import java.sql.PreparedStatement; +import java.util.function.Consumer; + +public interface PreparedQueryAction extends QueryAction { + + /** + * 设定SQL语句中所有 ? 对应的参数 + * + * @param params 参数内容 + * @return {@link PreparedQueryAction} + */ + PreparedQueryAction setParams(@Nullable Object... params); + + /** + * 设定SQL语句中所有 ? 对应的参数 + * + * @param params 参数内容 + * @return {@link PreparedQueryAction} + */ + PreparedQueryAction setParams(@Nullable Iterable params); + + /** + * 直接对 {@link PreparedStatement} 进行处理 + * + * @param statement {@link Consumer} 处理操作 + * 若为空则不进行处理 + * @return {@link PreparedQueryAction} + */ + PreparedQueryAction handleStatement(@Nullable Consumer statement); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/action/query/QueryAction.java b/src/main/java/com/io/yutian/aulib/sql/api/action/query/QueryAction.java new file mode 100644 index 0000000..44e853a --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/action/query/QueryAction.java @@ -0,0 +1,45 @@ +package com.io.yutian.aulib.sql.api.action.query; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.SQLQuery; +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import com.io.yutian.aulib.sql.api.function.SQLHandler; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; + +/** + * SQLQueryAction 是用于承载SQL查询语句并进行处理、返回并自动关闭连接的基本类。 + * + *
    + *
  • 同步执行 {@link #execute()}, {@link #execute(SQLFunction, SQLExceptionHandler)} + *
    同步执行方法中有会抛出异常的方法与不抛出异常的方法, + *
    若选择不抛出异常,则返回值可能为空,需要特殊处理。
  • + * + *
  • 异步执行 {@link #executeAsync(SQLHandler, SQLExceptionHandler)} + *
    异步执行时将提供成功与异常两种处理方式 + *
    可自行选择是否对数据或异常进行处理 + *
    默认的异常处理器为 {@link #defaultExceptionHandler()}
  • + *
+ * + * 注意: 无论是否异步,都不需要自行关闭ResultSet,本API已自动关闭 + * + * @author CarmJos + * @since 0.2.6 + */ +public interface QueryAction extends SQLAction { + + @Override + @Contract("_,!null -> !null") + default @Nullable R executeFunction(@NotNull SQLFunction<@NotNull SQLQuery, R> function, + @Nullable R defaultResult) throws SQLException { + try (SQLQuery value = execute()) { + R result = function.apply(value); + return result == null ? defaultResult : result; + } + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/ConditionalBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/ConditionalBuilder.java new file mode 100644 index 0000000..d19b099 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/ConditionalBuilder.java @@ -0,0 +1,82 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.SQLBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Date; +import java.util.LinkedHashMap; + +public interface ConditionalBuilder, T extends SQLAction> extends SQLBuilder { + + /** + * 将现有条件构建完整的SQL语句用于执行。 + * + * @return {@link SQLAction} + */ + T build(); + + /** + * 设定限定的条目数 + * + * @param limit 条数限制 + * @return {@link B} + */ + B setLimit(int limit); + + /** + * 直接设定条件的源文本,不需要以WHERE开头。 + *
如 {@code id = 1 AND name = 'test' OR name = 'test2'} 。 + * + * @param condition 条件文本,不需要以WHERE开头。 + * @return {@link B} + */ + B setConditions(@Nullable String condition); + + /** + * 直接设定每个条件的文本与其对应数值,将以AND链接,且不需要以WHERE开头。 + *
条件如 {@code id = ? },问号将被以对应的数值填充。。 + * + * @param conditionSQLs 条件内容,将以AND链接,且不需要以WHERE开头。 + * @return {@link B} + */ + B setConditions(LinkedHashMap<@NotNull String, @Nullable Object> conditionSQLs); + + B addCondition(@Nullable String condition); + + B addCondition(@NotNull String columnName, @NotNull String operator, @Nullable Object queryValue); + + B addCondition(@NotNull String columnName, @Nullable Object queryValue); + + B addCondition(@NotNull String[] columnNames, @Nullable Object[] queryValues); + + B addNotNullCondition(@NotNull String columnName); + + /** + * 添加时间的限定条件。 若设定了开始时间,则限定条件为 {@code endMillis >= startMillis}; + * + * @param columnName 判断的行 + * @param startMillis 开始时间戳,若{@code <0}则不作限定 + * @param endMillis 结束时间戳,若{@code <0}则不作限定 + * @return {@link B} + */ + default B addTimeCondition(@NotNull String columnName, long startMillis, long endMillis) { + return addTimeCondition(columnName, + startMillis > 0 ? new Date(startMillis) : null, + endMillis > 0 ? new Date(endMillis) : null + ); + } + + /** + * 添加时间的限定条件。 若设定了开始时间,则限定条件为 {@code endDate >= startTime}; + * + * @param columnName 判断的行 + * @param startDate 开始时间,若为null则不作限定 + * @param endDate 结束时间,若为null则不作限定 + * @return {@link B} + */ + B addTimeCondition(@NotNull String columnName, @Nullable Date startDate, @Nullable Date endDate); + + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/DeleteBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/DeleteBuilder.java new file mode 100644 index 0000000..fbc3c13 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/DeleteBuilder.java @@ -0,0 +1,9 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; + +public interface DeleteBuilder extends ConditionalBuilder> { + + String getTableName(); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/InsertBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/InsertBuilder.java new file mode 100644 index 0000000..84a9673 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/InsertBuilder.java @@ -0,0 +1,19 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; + +import java.util.Arrays; +import java.util.List; + +public interface InsertBuilder> { + + String getTableName(); + + T setColumnNames(List columnNames); + + default T setColumnNames(String... columnNames) { + return setColumnNames(columnNames == null ? null : Arrays.asList(columnNames)); + } + + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/QueryBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/QueryBuilder.java new file mode 100644 index 0000000..3f6ef87 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/QueryBuilder.java @@ -0,0 +1,37 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLBuilder; +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import com.io.yutian.aulib.sql.api.action.query.QueryAction; +import org.jetbrains.annotations.NotNull; + +public interface QueryBuilder extends SQLBuilder { + + /** + * 通过一条 SQL语句创建查询。 + * 该方法使用 Statement 实现,请注意SQL注入风险! + * + * @param sql SQL语句 + * @return {@link QueryAction} + * @deprecated 存在SQL注入风险,建议使用 {@link QueryBuilder#withPreparedSQL(String)} + */ + @Deprecated + QueryAction withSQL(@NotNull String sql); + + /** + * 通过一条 SQL语句创建预查询 + * + * @param sql SQL语句 + * @return {@link PreparedQueryAction} + */ + PreparedQueryAction withPreparedSQL(@NotNull String sql); + + /** + * 创建表查询 + * + * @param tableName 表名 + * @return {@link TableQueryBuilder} + */ + TableQueryBuilder inTable(@NotNull String tableName); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/ReplaceBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/ReplaceBuilder.java new file mode 100644 index 0000000..45edbfd --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/ReplaceBuilder.java @@ -0,0 +1,25 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; + +import java.util.Arrays; +import java.util.List; + +/** + * REPLACE 语句用于将一组值更新进数据表中。 + *
执行后,将通过表中键判断该数据是否存在,若存在则用新数据替换原来的值,若不存在则会插入该数据。 + *
在使用REPLACE时,表与所给行列数据中必须包含唯一索引(或主键),且索引不得为空值,否则将等同于插入语句。 + * + * @param 最终构建出的 {@link SQLAction} 类型 + */ +public interface ReplaceBuilder> { + + String getTableName(); + + T setColumnNames(List columnNames); + + default T setColumnNames(String... columnNames) { + return setColumnNames(columnNames == null ? null : Arrays.asList(columnNames)); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/TableAlterBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableAlterBuilder.java new file mode 100644 index 0000000..abc4617 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableAlterBuilder.java @@ -0,0 +1,129 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.SQLBuilder; +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.api.enums.IndexType; +import com.io.yutian.aulib.sql.api.enums.NumberType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface TableAlterBuilder extends SQLBuilder { + + SQLAction renameTo(@NotNull String newTableName); + + SQLAction changeComment(@NotNull String newTableComment); + + SQLAction setAutoIncrementIndex(int index); + + SQLAction addIndex(@NotNull IndexType indexType, @Nullable String indexName, + @NotNull String columnName, @NotNull String... moreColumns); + + /** + * 为该表移除一个索引 + * + * @param indexName 索引名 + * @return {@link SQLUpdateAction} + */ + SQLAction dropIndex(@NotNull String indexName); + + /** + * 为该表移除一个外键 + * + * @param keySymbol 外键名 + * @return {@link SQLUpdateAction} + */ + SQLAction dropForeignKey(@NotNull String keySymbol); + + /** + * 为该表移除主键(须添加新主键) + * + * @return {@link SQLUpdateAction} + */ + SQLAction dropPrimaryKey(); + + /** + * 为表添加一列 + * + * @param columnName 列名 + * @param settings 列的相关设定 + * @return {@link SQLUpdateAction} + */ + default SQLAction addColumn(@NotNull String columnName, @NotNull String settings) { + return addColumn(columnName, settings, null); + } + + /** + * 为表添加一列 + * + * @param columnName 列名 + * @param settings 列的相关设定 + * @param afterColumn 该列增添到哪个列的后面, + *

该参数若省缺则放于最后一行 + *

若为 "" 则置于首行。 + * @return {@link SQLUpdateAction} + */ + SQLAction addColumn(@NotNull String columnName, @NotNull String settings, @Nullable String afterColumn); + + SQLAction renameColumn(@NotNull String columnName, @NotNull String newName); + + SQLAction modifyColumn(@NotNull String columnName, @NotNull String settings); + + default SQLAction modifyColumn(@NotNull String columnName, @NotNull String columnSettings, @NotNull String afterColumn) { + return modifyColumn(columnName, columnSettings + " AFTER `" + afterColumn + "`"); + } + + SQLAction removeColumn(@NotNull String columnName); + + SQLAction setColumnDefault(@NotNull String columnName, @NotNull String defaultValue); + + SQLAction removeColumnDefault(@NotNull String columnName); + + /** + * 为该表添加一个自增列 + *

自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @param numberType 数字类型,若省缺则为 {@link NumberType#INT} + * @param primary 是否为主键,若否则只为唯一键 + * @param unsigned 是否采用 UNSIGNED (即无负数,可以增加自增键的最高数,建议为true) + * @return {@link TableCreateBuilder} + */ + default SQLAction addAutoIncrementColumn(@NotNull String columnName, @Nullable NumberType numberType, + boolean primary, boolean unsigned) { + return addColumn(columnName, + (numberType == null ? NumberType.INT : numberType).name() + + (unsigned ? " UNSIGNED " : " ") + + "NOT NULL AUTO_INCREMENT " + (primary ? "PRIMARY KEY" : "UNIQUE KEY"), + "" + ); + } + + /** + * 为该表添加一个自增列 + *
自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @param numberType 数字类型,若省缺则为 {@link NumberType#INT} + * @return {@link TableAlterBuilder} + */ + default SQLAction addAutoIncrementColumn(@NotNull String columnName, @NotNull NumberType numberType) { + return addAutoIncrementColumn(columnName, numberType, false, true); + } + + + /** + * 为该表添加一个自增列 + *
自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @return {@link TableAlterBuilder} + */ + default SQLAction addAutoIncrementColumn(@NotNull String columnName) { + return addAutoIncrementColumn(columnName, NumberType.INT); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/TableCreateBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableCreateBuilder.java new file mode 100644 index 0000000..eb9a762 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableCreateBuilder.java @@ -0,0 +1,256 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLBuilder; +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.api.enums.ForeignKeyRule; +import com.io.yutian.aulib.sql.api.enums.IndexType; +import com.io.yutian.aulib.sql.api.enums.NumberType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; +import static com.io.yutian.aulib.sql.api.SQLBuilder.withQuote; + + +public interface TableCreateBuilder extends SQLBuilder { + + /** + * 将现有条件构建完整的SQL语句用于执行。 + * + * @return {@link SQLUpdateAction} + */ + SQLUpdateAction build(); + + @NotNull String getTableName(); + + /** + * 得到表的设定。 + *

若未使用 {@link #setTableSettings(String)} 方法则会采用 {@link #defaultTablesSettings()} 。 + * + * @return TableSettings + */ + @NotNull String getTableSettings(); + + TableCreateBuilder setTableSettings(@NotNull String settings); + + /** + * 设定表的标注,一般用于解释该表的作用。 + * + * @param comment 表标注 + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder setTableComment(@Nullable String comment); + + /** + * 直接设定表的所有列信息 + * + * @param columns 列的相关信息 (包括列设定) + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder setColumns(@NotNull String... columns); + + /** + * 为该表添加一个列 + * + * @param column 列的相关信息 + *
如 `uuid` VARCHAR(36) NOT NULL UNIQUE KEY + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder addColumn(@NotNull String column); + + /** + * 为该表添加一个列 + * + * @param columnName 列名 + * @param settings 列的设定 + *
如 VARCHAR(36) NOT NULL UNIQUE KEY + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addColumn(@NotNull String columnName, @NotNull String settings) { + Objects.requireNonNull(columnName, "columnName could not be null"); + return addColumn(withBackQuote(columnName) + " " + settings); + } + + /** + * 为该表添加一个列 + * + * @param columnName 列名 + * @param settings 列的设定 + *
如 VARCHAR(36) NOT NULL UNIQUE KEY + * @param comments 列的注解,用于解释该列数据的作用 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addColumn(@NotNull String columnName, @NotNull String settings, @NotNull String comments) { + return addColumn(columnName, settings + " COMMENT " + withQuote(comments)); + } + + /** + * 为该表添加一个自增列 + *

自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @param numberType 数字类型,若省缺则为 {@link NumberType#INT} + * @param asPrimaryKey 是否为主键,若为false则设定为唯一键 + * @param unsigned 是否采用 UNSIGNED (即无负数,可以增加自增键的最高数,建议为true) + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName, @Nullable NumberType numberType, + boolean asPrimaryKey, boolean unsigned); + + /** + * 为该表添加一个INT类型的自增主键列 + *

自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @param asPrimaryKey 是否为主键,若为false则设定为唯一键 + * @param unsigned 是否采用 UNSIGNED (即无负数,可以增加自增键的最高数,建议为true) + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName, + boolean asPrimaryKey, boolean unsigned) { + return addAutoIncrementColumn(columnName, NumberType.INT, asPrimaryKey, unsigned); + } + + + /** + * 为该表添加一个INT类型的自增列 + *

自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @param asPrimaryKey 是否为主键,若为false则设定为唯一键 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName, boolean asPrimaryKey) { + return addAutoIncrementColumn(columnName, asPrimaryKey, true); + } + + /** + * 为该表添加一个INT类型的自增主键列 + *

自增列强制要求为数字类型,非空,且为UNIQUE。 + *

注意:一个表只允许有一个自增列! + * + * @param columnName 列名 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName) { + return addAutoIncrementColumn(columnName, true); + } + + /** + * 设定表中的某列为索引或键。 + * + *

创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。 + *
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE 和DELETE。 + *
因此,请合理的设计索引。 + * + * @param type 索引类型 + * @param columnName 索引包含的列 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder setIndex(@NotNull String columnName, + @NotNull IndexType type) { + return setIndex(type, null, columnName); + } + + /** + * 设定表中的某列为索引或键。 + * + *

创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。 + *
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE 和DELETE。 + *
因此,请合理的设计索引。 + * + * @param type 索引类型 + * @param indexName 索引名称,缺省时将根据第一个索引列赋一个名称 + * @param columnName 索引包含的列 + * @param moreColumns 联合索引需要包含的列 + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder setIndex(@NotNull IndexType type, @Nullable String indexName, + @NotNull String columnName, @NotNull String... moreColumns); + + + /** + * 以本表位从表,为表中某列设定自参照外键(即自参照完整性)。 + * + *

外键约束(FOREIGN KEY)是表的一个特殊字段,经常与主键约束一起使用。 + *
外键用来建立主表与从表的关联关系,为两个表的数据建立连接,约束两个表中数据的一致性和完整性。 + *
主表删除某条记录时,从表中与之对应的记录也必须有相应的改变。 + * + * @param tableColumn 本表中的列 + * @param foreignColumn 外键关联表中对应的关联列,必须为目标表的主键,即 {@link IndexType#PRIMARY_KEY} + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addForeignKey(@NotNull String tableColumn, @NotNull String foreignColumn) { + return addForeignKey(tableColumn, getTableName(), foreignColumn); + } + + /** + * 以本表位从表,为表中某列设定外键。 + * + *

外键约束(FOREIGN KEY)是表的一个特殊字段,经常与主键约束一起使用。 + *
外键用来建立主表与从表的关联关系,为两个表的数据建立连接,约束两个表中数据的一致性和完整性。 + *
主表删除某条记录时,从表中与之对应的记录也必须有相应的改变。 + * + * @param tableColumn 本表中的列 + * @param foreignTable 外键关联主表,必须为已存在的表或本表,且必须有主键。 + * @param foreignColumn 外键关联主表中对应的关联列,须满足 + *

1. 为主表的主键,即 {@link IndexType#PRIMARY_KEY} + *

2. 数据类型必须和所要建立主键的列的数据类型相同。 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addForeignKey(@NotNull String tableColumn, + @NotNull String foreignTable, @NotNull String foreignColumn) { + return addForeignKey(tableColumn, null, foreignTable, foreignColumn); + } + + /** + * 以本表位从表,为表中某列设定外键。 + * + *

外键约束(FOREIGN KEY)是表的一个特殊字段,经常与主键约束一起使用。 + *
外键用来建立主表与从表的关联关系,为两个表的数据建立连接,约束两个表中数据的一致性和完整性。 + *
主表删除某条记录时,从表中与之对应的记录也必须有相应的改变。 + * + * @param tableColumn 本表中的列 + * @param constraintName 约束名,缺省时将使用参数自动生成,如 fk_[tableColumn]_[foreignTable] + * @param foreignTable 外键关联主表,必须为已存在的表或本表,且必须有主键。 + * @param foreignColumn 外键关联主表中对应的关联列,须满足 + *

1. 为主表的主键,即 {@link IndexType#PRIMARY_KEY} + *

2. 数据类型必须和所要建立主键的列的数据类型相同。 + * @return {@link TableCreateBuilder} + */ + default TableCreateBuilder addForeignKey(@NotNull String tableColumn, @Nullable String constraintName, + @NotNull String foreignTable, @NotNull String foreignColumn) { + return addForeignKey(tableColumn, constraintName, foreignTable, foreignColumn, null, null); + } + + /** + * 以本表位从表,为表中某列设定外键。 + * + *

外键约束(FOREIGN KEY)是表的一个特殊字段,经常与主键约束一起使用。 + *
外键用来建立主表与从表的关联关系,为两个表的数据建立连接,约束两个表中数据的一致性和完整性。 + *
主表删除某条记录时,从表中与之对应的记录也必须有相应的改变。 + * + * @param tableColumn 本表中的列 + * @param constraintName 约束名,缺省时将使用参数自动生成,如 fk_[tableColumn]_[foreignTable] + * @param foreignTable 外键关联主表,必须为已存在的表或本表,且必须有主键。 + * @param foreignColumn 外键关联主表中对应的关联列,须满足 + *

1. 为主表的主键,即 {@link IndexType#PRIMARY_KEY} + *

2. 数据类型必须和所要建立主键的列的数据类型相同。 + * @param updateRule 在外键被更新时采用的规则,缺省时默认为{@link ForeignKeyRule#RESTRICT} + * @param deleteRule 在外键被删除时采用的规则,缺省时默认为{@link ForeignKeyRule#RESTRICT} + * @return {@link TableCreateBuilder} + */ + TableCreateBuilder addForeignKey(@NotNull String tableColumn, @Nullable String constraintName, + @NotNull String foreignTable, @NotNull String foreignColumn, + @Nullable ForeignKeyRule updateRule, @Nullable ForeignKeyRule deleteRule); + + default String defaultTablesSettings() { + return "ENGINE=InnoDB DEFAULT CHARSET=utf8"; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/TableMetadataBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableMetadataBuilder.java new file mode 100644 index 0000000..3aec6c1 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableMetadataBuilder.java @@ -0,0 +1,56 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLBuilder; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.sql.ResultSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public interface TableMetadataBuilder extends SQLBuilder { + + /** + * @return 本表是否存在 + */ + CompletableFuture validateExist(); + + /** + * 对表内的数据列元数据进行读取 + * + * @param columnPattern 列的名称匹配表达式, 为空则匹配所有列 + * @param reader 读取的方法 + * @param 结果类型 + * @return 读取结果 + */ + CompletableFuture fetchColumns(@Nullable String columnPattern, + @NotNull SQLFunction reader); + + /** + * @param columnPattern 需要判断的列名表达式 + * @return 对应列是否存在 + */ + CompletableFuture isColumnExists(@NotNull String columnPattern); + + /** + * 列出所有表内的全部列。 + * + * @return 表内全部数据列的列名 + */ + default CompletableFuture<@Unmodifiable Set> listColumns() { + return listColumns(null); + } + + /** + * 列出所有满足表达式的列。 + * + * @param columnPattern 列名表达式,为空则列出全部 + * @return 所有满足表达式的列名 + */ + CompletableFuture<@Unmodifiable Set> listColumns(@Nullable String columnPattern); + + // More coming soon. + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/TableQueryBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableQueryBuilder.java new file mode 100644 index 0000000..6935825 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/TableQueryBuilder.java @@ -0,0 +1,37 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import org.jetbrains.annotations.NotNull; + +public interface TableQueryBuilder extends ConditionalBuilder { + + @NotNull String getTableName(); + + /** + * 选定用于查询的列名 + * + * @param columnNames 列名 + * @return {@link TableQueryBuilder} + */ + TableQueryBuilder selectColumns(@NotNull String... columnNames); + + /** + * 对结果进行排序 + * + * @param columnName 排序使用的列名 + * @param asc 是否为正序排序 (为false则倒序排序) + * @return {@link TableQueryBuilder} + */ + TableQueryBuilder orderBy(@NotNull String columnName, boolean asc); + + /** + * 限制查询条数,用于分页查询。 + * + * @param start 开始数 + * @param end 结束条数 + * @return {@link TableQueryBuilder} + * @since 0.2.6 + */ + TableQueryBuilder setPageLimit(int start, int end); + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/UpdateBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/UpdateBuilder.java new file mode 100644 index 0000000..ed68991 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/UpdateBuilder.java @@ -0,0 +1,56 @@ +package com.io.yutian.aulib.sql.api.builder; + +import com.io.yutian.aulib.sql.api.SQLAction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; + +public interface UpdateBuilder extends ConditionalBuilder> { + + String getTableName(); + + /** + * 添加一条需要更新的字段名与值 + * + * @param columnName 字段名 + * @param columnValue 字段名对应的值 + * @return {@link UpdateBuilder} + * @since 0.3.7 + */ + UpdateBuilder addColumnValue(@NotNull String columnName, @Nullable Object columnValue); + + /** + * 设定更新的全部字段值 (此操作会覆盖之前的设定) + *

此操作会覆盖之前的设定 + * + * @param columnData 字段名和值的键值对 + * @return {@link UpdateBuilder} + */ + UpdateBuilder setColumnValues(LinkedHashMap<@NotNull String, @Nullable Object> columnData); + + /** + * 设定更新的全部字段值 (此操作会覆盖之前的设定) + *

此操作会覆盖之前的设定 + * + * @param columnNames 字段名 + * @param columnValues 字段名对应的值 + * @return {@link UpdateBuilder} + */ + UpdateBuilder setColumnValues(@NotNull String[] columnNames, @Nullable Object[] columnValues); + + /** + * 设定更新的全部字段值 (此操作会覆盖之前的设定) + *

如需同时更新多条字段,请使用 {@link #setColumnValues(String[], Object[])} 或 {@link #setColumnValues(LinkedHashMap)} + *
也可以使用 {@link #addColumnValue(String, Object)} 一条条的添加字段 + * + * @param columnName 字段名 + * @param columnValue 字段名对应的值 + * @return {@link UpdateBuilder} + */ + default UpdateBuilder setColumnValues(@NotNull String columnName, @Nullable Object columnValue) { + return setColumnValues(new String[]{columnName}, new Object[]{columnValue}); + } + + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/builder/UpsertBuilder.java b/src/main/java/com/io/yutian/aulib/sql/api/builder/UpsertBuilder.java new file mode 100644 index 0000000..b9d64a3 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/builder/UpsertBuilder.java @@ -0,0 +1,17 @@ +package com.io.yutian.aulib.sql.api.builder; + +/** + * 存在则更新,不存在则插入。 + * + * @see ReplaceBuilder + */ +@Deprecated +public interface UpsertBuilder { + + String getTableName(); + + default UpsertBuilder setColumnNames(String[] columnNames, String updateColumn) { + throw new UnsupportedOperationException("Please use REPLACE ."); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/enums/ForeignKeyRule.java b/src/main/java/com/io/yutian/aulib/sql/api/enums/ForeignKeyRule.java new file mode 100644 index 0000000..8efef25 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/enums/ForeignKeyRule.java @@ -0,0 +1,41 @@ +package com.io.yutian.aulib.sql.api.enums; + +public enum ForeignKeyRule { + + /** + * 啥也不做 + *

注意: 在Mysql中该选项实际上等同于采用默认的 {@link #RESTRICT} 设定! + */ + NO_ACTION("NO ACTION"), + + /** + * 拒绝删除要求,直到使用删除键值的辅助表被手工删除,并且没有参照时(这是默认设置,也是最安全的设置) + */ + RESTRICT("RESTRICT"), + + /** + * 修改包含与已删除键值有参照关系的所有记录,使用NULL值替换(只能用于已标记为NOT NULL的字段) + */ + SET_NULL("SET NULL"), + + /** + * 修改包含与已删除键值有参照关系的所有记录,使用默认值替换(只能用于设定了DEFAULT的字段) + */ + SET_DEFAULT("SET DEFAULT"), + + /** + * 级联删除,删除包含与已删除键值有参照关系的所有记录 + */ + CASCADE("CASCADE"); + + final String ruleName; + + ForeignKeyRule(String ruleName) { + this.ruleName = ruleName; + } + + public String getRuleName() { + return ruleName; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/enums/IndexType.java b/src/main/java/com/io/yutian/aulib/sql/api/enums/IndexType.java new file mode 100644 index 0000000..5da665d --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/enums/IndexType.java @@ -0,0 +1,41 @@ +package com.io.yutian.aulib.sql.api.enums; + +public enum IndexType { + + + /** + * 普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。 + *
因此,应该只为那些最经常出现在查询条件(WHERE column=)或排序条件(ORDER BY column)中的数据列创建索引。 + *
只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如一个整数类型的数据列)来创建索引。 + */ + INDEX("INDEX"), + + + /** + * 唯一索引 是在表上一个或者多个字段组合建立的索引,这个或者这些字段的值组合起来在表中不可以重复,用于保证数据的唯一性。 + */ + UNIQUE_KEY("UNIQUE KEY"), + + /** + * 主键索引 是唯一索引的特定类型。表中创建主键时自动创建的索引 。一个表只能建立一个主索引。 + */ + PRIMARY_KEY("PRIMARY KEY"), + + /** + * 全文索引 主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 + *
请搭配 MATCH 等语句使用,而不是使用 WHERE - LIKE 。 + *
全文索引只可用于 CHAR、 VARCHAR 与 TEXT 系列类型。 + */ + FULLTEXT_INDEX("FULLTEXT"); + + + final String name; + + IndexType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/enums/NumberType.java b/src/main/java/com/io/yutian/aulib/sql/api/enums/NumberType.java new file mode 100644 index 0000000..88e450d --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/enums/NumberType.java @@ -0,0 +1,11 @@ +package com.io.yutian.aulib.sql.api.enums; + +public enum NumberType { + + TINYINT, + SMALLINT, + MEDIUMINT, + INT, + BIGINT + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/function/SQLBiFunction.java b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLBiFunction.java new file mode 100644 index 0000000..48fdb2f --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLBiFunction.java @@ -0,0 +1,24 @@ +package com.io.yutian.aulib.sql.api.function; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; +import java.util.Objects; + +@FunctionalInterface +public interface SQLBiFunction { + + @Nullable + R apply(@NotNull T t, @NotNull U u) throws SQLException; + + default SQLBiFunction then(@NotNull SQLFunction after) { + Objects.requireNonNull(after); + return (T t, U u) -> { + R r = apply(t, u); + if (r == null) return null; + else return after.apply(r); + }; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/function/SQLDebugHandler.java b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLDebugHandler.java new file mode 100644 index 0000000..ef52996 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLDebugHandler.java @@ -0,0 +1,100 @@ +package com.io.yutian.aulib.sql.api.function; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.SQLQuery; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateBatchAction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 异常处理器。 + *
在使用 {@link SQLAction#execute(SQLExceptionHandler)} 等相关方法时, + * 如果发生异常,则会调用错误处理器进行错误内容的输出提示。 + */ + +public interface SQLDebugHandler { + /** + * 该方法将在 {@link SQLAction#execute()} 执行前调用。 + * + * @param action {@link SQLAction} 对象 + * @param params 执行传入的参数列表。 + * 实际上,仅有 {@link PreparedSQLUpdateAction} 和 {@link PreparedSQLUpdateBatchAction} 才会有传入参数。 + */ + void beforeExecute(@NotNull SQLAction action, @NotNull List<@Nullable Object[]> params); + + /** + * 该方法将在 {@link SQLQuery#close()} 执行后调用。 + * + * @param query {@link SQLQuery} 对象 + * @param executeNanoTime 该次查询开始执行的时间 (单位:纳秒) + * @param closeNanoTime 该次查询彻底关闭的时间 (单位:纳秒) + */ + void afterQuery(@NotNull SQLQuery query, long executeNanoTime, long closeNanoTime); + + default String parseParams(@Nullable Object[] params) { + if (params == null) return "<#NULL>"; + else if (params.length == 0) return "<#EMPTY>"; + + List paramsString = new ArrayList<>(); + for (Object param : params) { + if (param == null) paramsString.add("NULL"); + else paramsString.add(param.toString()); + } + return String.join(", ", paramsString); + } + + @SuppressWarnings("DuplicatedCode") + static SQLDebugHandler defaultHandler(Logger logger) { + return new SQLDebugHandler() { + + @Override + public void beforeExecute(@NotNull SQLAction action, @NotNull List<@Nullable Object[]> params) { + logger.info("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + logger.info("┣# ActionUUID: {}", action.getActionUUID()); + logger.info("┣# ActionType: {}", action.getClass().getSimpleName()); + if (action.getSQLContents().size() == 1) { + logger.info("┣# SQLContent: {}", action.getSQLContents().get(0)); + } else { + logger.info("┣# SQLContents: "); + int i = 0; + for (String sqlContent : action.getSQLContents()) { + logger.info("┃ - [{}] {}", ++i, sqlContent); + } + } + if (params.size() == 1) { + Object[] param = params.get(0); + if (param != null) { + logger.info("┣# SQLParam: {}", parseParams(param)); + } + } else if (params.size() > 1) { + logger.info("┣# SQLParams: "); + int i = 0; + for (Object[] param : params) { + logger.info("┃ - [{}] {}", ++i, parseParams(param)); + } + } + logger.info("┣# CreateTime: {}", action.getCreateTime(TimeUnit.MILLISECONDS)); + logger.info("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + } + + @Override + public void afterQuery(@NotNull SQLQuery query, long executeNanoTime, long closeNanoTime) { + logger.info("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + logger.info("┣# ActionUUID: {}", query.getAction().getActionUUID()); + logger.info("┣# SQLContent: {}", query.getSQLContent()); + logger.info("┣# CloseTime: {} (cost {} ms)", + TimeUnit.NANOSECONDS.toMillis(closeNanoTime), + ((double) (closeNanoTime - executeNanoTime) / 1000000) + ); + logger.info("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + } + }; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/function/SQLExceptionHandler.java b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLExceptionHandler.java new file mode 100644 index 0000000..3c7c733 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLExceptionHandler.java @@ -0,0 +1,46 @@ +package com.io.yutian.aulib.sql.api.function; + +import com.io.yutian.aulib.sql.api.SQLAction; +import org.slf4j.Logger; + +import java.sql.SQLException; +import java.util.function.BiConsumer; + +/** + * 异常处理器。 + *
在使用 {@link SQLAction#execute(SQLExceptionHandler)} 等相关方法时, + * 如果发生异常,则会调用错误处理器进行错误内容的输出提示。 + */ +@FunctionalInterface +public interface SQLExceptionHandler extends BiConsumer> { + + /** + * 默认的异常处理器,将详细的输出相关错误与错误来源。 + * + * @param logger 用于输出错误信息的Logger。 + * @return 输出详细信息的错误处理器。 + */ + static SQLExceptionHandler detailed(Logger logger) { + return (exception, sqlAction) -> { + logger.error("Error occurred while executing SQL: "); + int i = 1; + for (String content : sqlAction.getSQLContents()) { + logger.error(String.format("#%d {%s}", i, content)); + i++; + } + exception.printStackTrace(); + }; + } + + /** + * “安静“ 的错误处理器,发生错误什么都不做。 + * 强烈不建议把此处理器作为默认处理器使用! + * + * @return 无输出的处理器。 + */ + static SQLExceptionHandler silent() { + return (exception, sqlAction) -> { + }; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/function/SQLFunction.java b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLFunction.java new file mode 100644 index 0000000..63910b9 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLFunction.java @@ -0,0 +1,34 @@ +package com.io.yutian.aulib.sql.api.function; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; +import java.util.Objects; + +@FunctionalInterface +public interface SQLFunction { + + @Nullable + R apply(@NotNull T t) throws SQLException; + + default SQLFunction compose(@NotNull SQLFunction before) { + Objects.requireNonNull(before); + return (V v) -> { + T t = before.apply(v); + if (t == null) return null; + else return apply(t); + }; + } + + default SQLFunction then(@NotNull SQLFunction after) { + Objects.requireNonNull(after); + return (T t) -> { + R r = apply(t); + if (r == null) return null; + else return after.apply(r); + }; + } + + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/function/SQLHandler.java b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLHandler.java new file mode 100644 index 0000000..16d2904 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/function/SQLHandler.java @@ -0,0 +1,23 @@ +package com.io.yutian.aulib.sql.api.function; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.sql.SQLException; +import java.util.Objects; + +@FunctionalInterface +public interface SQLHandler { + + void accept(@NotNull T t) throws SQLException; + + @NotNull + @Contract(pure = true) + default SQLHandler andThen(@NotNull SQLHandler after) { + Objects.requireNonNull(after); + return (T t) -> { + accept(t); + after.accept(t); + }; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/table/NamedSQLTable.java b/src/main/java/com/io/yutian/aulib/sql/api/table/NamedSQLTable.java new file mode 100644 index 0000000..c14e935 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/table/NamedSQLTable.java @@ -0,0 +1,50 @@ +package com.io.yutian.aulib.sql.api.table; + +import com.io.yutian.aulib.sql.api.SQLManager; +import com.io.yutian.aulib.sql.api.SQLTable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.SQLException; + + +public abstract class NamedSQLTable implements SQLTable { + + private final @NotNull String tableName; + + protected @Nullable String tablePrefix; + protected @Nullable SQLManager manager; + + /** + * 请调用 {@link NamedSQLTable} 下的静态方法进行对象的初始化。 + * + * @param tableName 该表的名称 + */ + public NamedSQLTable(@NotNull String tableName) { + this.tableName = tableName; + } + + public @NotNull String getTableName() { + return (tablePrefix != null ? tablePrefix : "") + tableName; + } + + @Override + public @Nullable SQLManager getSQLManager() { + return this.manager; + } + + /** + * 使用指定 SQLManager 进行本示例的初始化。 + * + * @param sqlManager {@link SQLManager} + * @param tablePrefix 表名前缀 + * @return 本表是否为首次创建 + * @throws SQLException 出现任何错误时抛出 + */ + public abstract boolean create(@NotNull SQLManager sqlManager, @Nullable String tablePrefix) throws SQLException; + + public boolean create(@NotNull SQLManager sqlManager) throws SQLException { + return create(sqlManager, null); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/util/TimeDateUtils.java b/src/main/java/com/io/yutian/aulib/sql/api/util/TimeDateUtils.java new file mode 100644 index 0000000..b70598f --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/util/TimeDateUtils.java @@ -0,0 +1,108 @@ +package com.io.yutian.aulib.sql.api.util; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class TimeDateUtils { + public static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public TimeDateUtils() { + } + + /** + * 得到当前时间文本。 + * + * @return 时间文本 格式{@link TimeDateUtils#getFormat()} + */ + public static String getCurrentTime() { + return getTimeString(System.currentTimeMillis()); + } + + /** + * 得到一个时间戳的文本 + * + * @param timeMillis 时间戳 + * @return 时间文本 格式{@link TimeDateUtils#getFormat()} + */ + public static String getTimeString(long timeMillis) { + return getFormat().format(new Date(timeMillis)); + } + + /** + * 得到一个日期时间的文本 + * + * @param time 日期时间 + * @return 时间文本 格式{@link TimeDateUtils#getFormat()} + */ + public static String getTimeString(Date time) { + return getFormat().format(time); + } + + /** + * 得到一个时间文本的时间戳 + * + * @param timeString 时间文本 + * @return 时间戳 格式{@link TimeDateUtils#getFormat()} + */ + public static long parseTimeMillis(String timeString) { + if (timeString == null) { + return -1L; + } else { + try { + return format.parse(timeString).getTime(); + } catch (ParseException var2) { + return -1L; + } + } + } + + + /** + * 得到一个时间文本的对应日期实例 + * + * @param timeString 时间文本 + * @return 日期实例 格式{@link TimeDateUtils#getFormat()} + */ + public static Date getTimeDate(String timeString) { + if (timeString == null) { + return null; + } else { + try { + return format.parse(timeString); + } catch (ParseException var2) { + return null; + } + } + } + + /** + * 将秒数转化为 DD:hh:mm:ss 格式 + * + * @param allSeconds 秒数 + * @return DD:hh:mm:ss格式文本 + */ + public static String toDHMSStyle(long allSeconds) { + long days = allSeconds / 86400L; + long hours = allSeconds % 86400L / 3600L; + long minutes = allSeconds % 3600L / 60L; + long seconds = allSeconds % 60L; + String DateTimes; + if (days > 0L) { + DateTimes = days + "天" + (hours > 0L ? hours + "小时" : "") + (minutes > 0L ? minutes + "分钟" : "") + (seconds > 0L ? seconds + "秒" : ""); + } else if (hours > 0L) { + DateTimes = hours + "小时" + (minutes > 0L ? minutes + "分钟" : "") + (seconds > 0L ? seconds + "秒" : ""); + } else if (minutes > 0L) { + DateTimes = minutes + "分钟" + (seconds > 0L ? seconds + "秒" : ""); + } else { + DateTimes = seconds + "秒"; + } + + return DateTimes; + } + + public static DateFormat getFormat() { + return format; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/api/util/UUIDUtil.java b/src/main/java/com/io/yutian/aulib/sql/api/util/UUIDUtil.java new file mode 100644 index 0000000..eff4c77 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/api/util/UUIDUtil.java @@ -0,0 +1,28 @@ +package com.io.yutian.aulib.sql.api.util; + +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UUIDUtil { + + private static final Pattern COMPILE = Pattern.compile("-", Pattern.LITERAL); + + public static UUID random() { + return UUID.randomUUID(); + } + + public static String toString(UUID uuid, boolean withDash) { + if (withDash) return uuid.toString(); + else return COMPILE.matcher(uuid.toString()).replaceAll(Matcher.quoteReplacement("")); + } + + public static UUID toUUID(String s) { + if (s.length() == 36) { + return UUID.fromString(s); + } else { + return UUID.fromString(s.substring(0, 8) + '-' + s.substring(8, 12) + '-' + s.substring(12, 16) + '-' + s.substring(16, 20) + '-' + s.substring(20)); + } + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/AbstractSQLBuilder.java b/src/main/java/com/io/yutian/aulib/sql/builder/AbstractSQLBuilder.java new file mode 100644 index 0000000..772acad --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/AbstractSQLBuilder.java @@ -0,0 +1,23 @@ +package com.io.yutian.aulib.sql.builder; + +import com.io.yutian.aulib.sql.api.SQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public abstract class AbstractSQLBuilder implements SQLBuilder { + + @NotNull + final SQLManagerImpl sqlManager; + + public AbstractSQLBuilder(@NotNull SQLManagerImpl manager) { + Objects.requireNonNull(manager, "SQLManager must not be null"); + this.sqlManager = manager; + } + + @Override + public @NotNull SQLManagerImpl getManager() { + return this.sqlManager; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/AbstractConditionalBuilder.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/AbstractConditionalBuilder.java new file mode 100644 index 0000000..f331ac2 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/AbstractConditionalBuilder.java @@ -0,0 +1,159 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.builder.ConditionalBuilder; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.Time; +import java.sql.Timestamp; +import java.util.*; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; + +public abstract class AbstractConditionalBuilder, T extends SQLAction> + extends AbstractSQLBuilder implements ConditionalBuilder { + + ArrayList conditionSQLs = new ArrayList<>(); + ArrayList conditionParams = new ArrayList<>(); + int limit = -1; + + public AbstractConditionalBuilder(@NotNull SQLManagerImpl manager) { + super(manager); + } + + protected abstract B getThis(); + + @Override + public B setConditions(@Nullable String condition) { + this.conditionSQLs = new ArrayList<>(); + this.conditionParams = new ArrayList<>(); + if (condition != null) this.conditionSQLs.add(condition); + return getThis(); + } + + @Override + public B setConditions( + LinkedHashMap<@NotNull String, @Nullable Object> conditions + ) { + conditions.forEach(this::addCondition); + return getThis(); + } + + @Override + public B addCondition(@Nullable String condition) { + this.conditionSQLs.add(condition); + return getThis(); + } + + @Override + public B addCondition(@NotNull String columnName, @Nullable Object queryValue) { + Objects.requireNonNull(columnName, "columnName could not be null"); + if (queryValue == null) { + return addCondition(withBackQuote(columnName) + " IS NULL"); + } else { + return addCondition(columnName, "=", queryValue); + } + } + + @Override + public B addCondition( + @NotNull String columnName, @NotNull String operator, @Nullable Object queryValue + ) { + Objects.requireNonNull(columnName, "columnName could not be null"); + Objects.requireNonNull(operator, "operator could not be null (e.g. > or = or <) "); + addCondition(withBackQuote(columnName) + " " + operator + " ?"); + this.conditionParams.add(queryValue); + return getThis(); + } + + @Override + public B addCondition( + @NotNull String[] columnNames, @Nullable Object[] queryValues + ) { + Objects.requireNonNull(columnNames, "columnName could not be null"); + if (queryValues == null || columnNames.length != queryValues.length) { + throw new RuntimeException("queryNames are not match with queryValues"); + } + for (int i = 0; i < columnNames.length; i++) { + addCondition(columnNames[i], queryValues[i]); + } + return getThis(); + } + + + @Override + public B addNotNullCondition(@NotNull String columnName) { + Objects.requireNonNull(columnName, "columnName could not be null"); + return addCondition(withBackQuote(columnName) + " IS NOT NULL"); + } + + + @Override + public B addTimeCondition( + @NotNull String columnName, @Nullable Date startDate, @Nullable Date endDate + ) { + Objects.requireNonNull(columnName, "columnName could not be null"); + if (startDate == null && endDate == null) return getThis(); // 都不限定时间,不用判断了 + if (startDate != null) { + addCondition(withBackQuote(columnName) + " BETWEEN ? AND ?"); + this.conditionParams.add(startDate); + if (endDate != null) { + this.conditionParams.add(endDate); + } else { + if (startDate instanceof java.sql.Date) { + this.conditionParams.add(new java.sql.Date(System.currentTimeMillis())); + } else if (startDate instanceof Time) { + this.conditionParams.add(new Time(System.currentTimeMillis())); + } else { + this.conditionParams.add(new Timestamp(System.currentTimeMillis())); + } + } + } else { + addCondition(columnName, "<=", endDate); + } + return getThis(); + } + + + @Override + public B setLimit(int limit) { + this.limit = limit; + return getThis(); + } + + protected String buildConditionSQL() { + + if (!conditionSQLs.isEmpty()) { + StringBuilder conditionBuilder = new StringBuilder(); + conditionBuilder.append("WHERE").append(" "); + Iterator iterator = conditionSQLs.iterator(); + while (iterator.hasNext()) { + conditionBuilder.append(iterator.next()); + if (iterator.hasNext()) conditionBuilder.append(" AND "); + } + return conditionBuilder.toString().trim(); + } else { + return null; + } + + } + + protected String buildLimitSQL() { + return limit > 0 ? "LIMIT " + limit : ""; + } + + protected ArrayList getConditionParams() { + return conditionParams; + } + + protected boolean hasConditions() { + return this.conditionSQLs != null && !this.conditionSQLs.isEmpty(); + } + + protected boolean hasConditionParams() { + return hasConditions() && getConditionParams() != null && !getConditionParams().isEmpty(); + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/DeleteBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/DeleteBuilderImpl.java new file mode 100644 index 0000000..8bea2b0 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/DeleteBuilderImpl.java @@ -0,0 +1,53 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.PreparedSQLUpdateActionImpl; +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.builder.DeleteBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; + +public class DeleteBuilderImpl + extends AbstractConditionalBuilder> + implements DeleteBuilder { + + protected final String tableName; + + public DeleteBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + Objects.requireNonNull(tableName); + this.tableName = tableName; + } + + @Override + public PreparedSQLUpdateAction build() { + + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("DELETE FROM ").append(withBackQuote(getTableName())); + + if (hasConditions()) sqlBuilder.append(" ").append(buildConditionSQL()); + if (limit > 0) sqlBuilder.append(" ").append(buildLimitSQL()); + + return new PreparedSQLUpdateActionImpl<>( + getManager(), Integer.class, sqlBuilder.toString(), + (hasConditionParams() ? getConditionParams() : null) + ); + } + + @Override + public String getTableName() { + return tableName; + } + + + @Override + protected DeleteBuilderImpl getThis() { + return this; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/InsertBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/InsertBuilderImpl.java new file mode 100644 index 0000000..972798d --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/InsertBuilderImpl.java @@ -0,0 +1,57 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.builder.InsertBuilder; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; + +public abstract class InsertBuilderImpl> + extends AbstractSQLBuilder implements InsertBuilder { + + protected final String tableName; + + public InsertBuilderImpl(@NotNull SQLManagerImpl manager, String tableName) { + super(manager); + Objects.requireNonNull(tableName); + this.tableName = tableName; + } + + protected static String buildSQL(String tableName, List columnNames) { + return buildSQL("INSERT IGNORE INTO", tableName, columnNames); + } + + protected static String buildSQL(String sqlPrefix, String tableName, List columnNames) { + int valueLength = columnNames.size(); + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(sqlPrefix).append(" ").append(withBackQuote(tableName)).append("("); + Iterator iterator = columnNames.iterator(); + while (iterator.hasNext()) { + sqlBuilder.append(withBackQuote(iterator.next())); + if (iterator.hasNext()) sqlBuilder.append(", "); + } + + sqlBuilder.append(") VALUES ("); + + for (int i = 0; i < valueLength; i++) { + sqlBuilder.append("?"); + if (i != valueLength - 1) { + sqlBuilder.append(", "); + } + } + sqlBuilder.append(")"); + return sqlBuilder.toString(); + } + + @Override + public String getTableName() { + return tableName; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/QueryBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/QueryBuilderImpl.java new file mode 100644 index 0000000..a1f84cb --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/QueryBuilderImpl.java @@ -0,0 +1,39 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.query.PreparedQueryActionImpl; +import com.io.yutian.aulib.sql.action.query.QueryActionImpl; +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import com.io.yutian.aulib.sql.api.action.query.QueryAction; +import com.io.yutian.aulib.sql.api.builder.QueryBuilder; +import com.io.yutian.aulib.sql.api.builder.TableQueryBuilder; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class QueryBuilderImpl extends AbstractSQLBuilder implements QueryBuilder { + public QueryBuilderImpl(@NotNull SQLManagerImpl manager) { + super(manager); + } + + @Override + @Deprecated + public QueryAction withSQL(@NotNull String sql) { + Objects.requireNonNull(sql, "sql could not be null"); + return new QueryActionImpl(getManager(), sql); + } + + @Override + public PreparedQueryAction withPreparedSQL(@NotNull String sql) { + Objects.requireNonNull(sql, "sql could not be null"); + return new PreparedQueryActionImpl(getManager(), sql); + } + + @Override + public TableQueryBuilder inTable(@NotNull String tableName) { + Objects.requireNonNull(tableName, "tableName could not be null"); + return new TableQueryBuilderImpl(getManager(), tableName); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/ReplaceBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/ReplaceBuilderImpl.java new file mode 100644 index 0000000..48700bf --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/ReplaceBuilderImpl.java @@ -0,0 +1,29 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.builder.ReplaceBuilder; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public abstract class ReplaceBuilderImpl> + extends AbstractSQLBuilder implements ReplaceBuilder { + + protected final @NotNull String tableName; + + public ReplaceBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + this.tableName = tableName; + } + + protected static String buildSQL(String tableName, List columnNames) { + return InsertBuilderImpl.buildSQL("REPLACE INTO", tableName, columnNames); + } + + @Override + public @NotNull String getTableName() { + return tableName; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableAlterBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableAlterBuilderImpl.java new file mode 100644 index 0000000..8bee797 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableAlterBuilderImpl.java @@ -0,0 +1,147 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.SQLUpdateActionImpl; +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.builder.TableAlterBuilder; +import com.io.yutian.aulib.sql.api.enums.IndexType; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; +import static com.io.yutian.aulib.sql.api.SQLBuilder.withQuote; + +public class TableAlterBuilderImpl extends AbstractSQLBuilder implements TableAlterBuilder { + + protected final @NotNull String tableName; + + public TableAlterBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + this.tableName = tableName; + } + + public @NotNull String getTableName() { + return tableName; + } + + @Override + public SQLAction renameTo(@NotNull String newTableName) { + Objects.requireNonNull(newTableName, "table name could not be null"); + return createAction("ALTER TABLE " + withBackQuote(tableName) + " RENAME TO " + withBackQuote(newTableName)); + } + + @Override + public SQLAction changeComment(@NotNull String newTableComment) { + Objects.requireNonNull(newTableComment, "table comment could not be null"); + return createAction("ALTER TABLE " + withBackQuote(getTableName()) + " COMMENT " + withQuote(newTableComment)); + } + + @Override + public SQLAction setAutoIncrementIndex(int index) { + return createAction("ALTER TABLE " + withBackQuote(getTableName()) + " AUTO_INCREMENT=" + index); + } + + @Override + public SQLAction addIndex(@NotNull IndexType indexType, @Nullable String indexName, + @NotNull String columnName, @NotNull String... moreColumns) { + Objects.requireNonNull(indexType, "indexType could not be null"); + Objects.requireNonNull(columnName, "column names could not be null"); + Objects.requireNonNull(moreColumns, "column names could not be null"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " ADD " + + TableCreateBuilderImpl.buildIndexSettings(indexType, indexName, columnName, moreColumns) + ); + } + + @Override + public SQLAction dropIndex(@NotNull String indexName) { + Objects.requireNonNull(indexName, "indexName could not be null"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " DROP INDEX " + withBackQuote(indexName) + ); + } + + @Override + public SQLAction dropForeignKey(@NotNull String keySymbol) { + Objects.requireNonNull(keySymbol, "keySymbol could not be null"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " DROP FOREIGN KEY " + withBackQuote(keySymbol) + ); + } + + @Override + public SQLAction dropPrimaryKey() { + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " DROP PRIMARY KEY" + ); + } + + @Override + public SQLAction addColumn(@NotNull String columnName, @NotNull String settings, @Nullable String afterColumn) { + Objects.requireNonNull(columnName, "columnName could not be null"); + Objects.requireNonNull(settings, "settings could not be null"); + String orderSettings = null; + if (afterColumn != null) { + if (afterColumn.length() > 0) { + orderSettings = "AFTER " + withBackQuote(afterColumn); + } else { + orderSettings = "FIRST"; + } + } + + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " ADD " + withBackQuote(columnName) + " " + settings + + (orderSettings != null ? " " + orderSettings : "") + ); + } + + @Override + public SQLAction renameColumn(@NotNull String columnName, @NotNull String newName) { + Objects.requireNonNull(columnName, "columnName could not be null"); + Objects.requireNonNull(newName, "please specify new column name"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " RENAME COLUMN " + withBackQuote(columnName) + " TO " + withBackQuote(newName) + ); + } + + @Override + public SQLAction modifyColumn(@NotNull String columnName, @NotNull String settings) { + Objects.requireNonNull(columnName, "columnName could not be null"); + Objects.requireNonNull(settings, "please specify new column settings"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " MODIFY COLUMN " + withBackQuote(columnName) + " " + settings + ); + } + + @Override + public SQLAction removeColumn(@NotNull String columnName) { + Objects.requireNonNull(columnName, "columnName could not be null"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " DROP " + withBackQuote(columnName) + ); + } + + @Override + public SQLAction setColumnDefault(@NotNull String columnName, @NotNull String defaultValue) { + Objects.requireNonNull(columnName, "columnName could not be null"); + Objects.requireNonNull(defaultValue, "defaultValue could not be null, if you need to remove the default value, please use #removeColumnDefault()."); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " ALTER " + withBackQuote(columnName) + " SET DEFAULT " + defaultValue + ); + } + + @Override + public SQLAction removeColumnDefault(@NotNull String columnName) { + Objects.requireNonNull(columnName, "columnName could not be null"); + return createAction( + "ALTER TABLE " + withBackQuote(getTableName()) + " ALTER " + withBackQuote(columnName) + " DROP DEFAULT" + ); + } + + private SQLUpdateActionImpl createAction(@NotNull String sql) { + return new SQLUpdateActionImpl<>(getManager(), Integer.class, sql); + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableCreateBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableCreateBuilderImpl.java new file mode 100644 index 0000000..7423708 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableCreateBuilderImpl.java @@ -0,0 +1,172 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.SQLUpdateActionImpl; +import com.io.yutian.aulib.sql.api.action.SQLUpdateAction; +import com.io.yutian.aulib.sql.api.builder.TableCreateBuilder; +import com.io.yutian.aulib.sql.api.enums.ForeignKeyRule; +import com.io.yutian.aulib.sql.api.enums.IndexType; +import com.io.yutian.aulib.sql.api.enums.NumberType; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; +import static com.io.yutian.aulib.sql.api.SQLBuilder.withQuote; + +public class TableCreateBuilderImpl extends AbstractSQLBuilder implements TableCreateBuilder { + + protected final @NotNull String tableName; + @NotNull + final List indexes = new ArrayList<>(); + @NotNull + final List foreignKeys = new ArrayList<>(); + @NotNull List columns = new ArrayList<>(); + @NotNull String tableSettings = defaultTablesSettings(); + @Nullable String tableComment; + + public TableCreateBuilderImpl(SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + this.tableName = tableName; + } + + protected static String buildIndexSettings(@NotNull IndexType indexType, @Nullable String indexName, + @NotNull String columnName, @NotNull String... moreColumns) { + Objects.requireNonNull(indexType, "indexType could not be null"); + Objects.requireNonNull(columnName, "column names could not be null"); + Objects.requireNonNull(moreColumns, "column names could not be null"); + StringBuilder indexBuilder = new StringBuilder(); + + indexBuilder.append(indexType.getName()).append(" "); + if (indexName != null) { + indexBuilder.append(withBackQuote(indexName)); + } + indexBuilder.append("("); + indexBuilder.append(withBackQuote(columnName)); + + if (moreColumns.length > 0) { + indexBuilder.append(", "); + + for (int i = 0; i < moreColumns.length; i++) { + indexBuilder.append(withBackQuote(moreColumns[i])); + if (i != moreColumns.length - 1) indexBuilder.append(", "); + } + + } + + indexBuilder.append(")"); + + return indexBuilder.toString(); + } + + @Override + public @NotNull String getTableName() { + return this.tableName; + } + + @Override + public @NotNull String getTableSettings() { + return this.tableSettings; + } + + @Override + public SQLUpdateAction build() { + StringBuilder createSQL = new StringBuilder(); + createSQL.append("CREATE TABLE IF NOT EXISTS ").append(withBackQuote(tableName)); + createSQL.append("("); + createSQL.append(String.join(", ", columns)); + if (indexes.size() > 0) { + createSQL.append(", "); + createSQL.append(String.join(", ", indexes)); + } + if (foreignKeys.size() > 0) { + createSQL.append(", "); + createSQL.append(String.join(", ", foreignKeys)); + } + createSQL.append(") ").append(getTableSettings()); + + if (tableComment != null) { + createSQL.append(" COMMENT ").append(withQuote(tableComment)); + } + + return new SQLUpdateActionImpl<>(getManager(), Integer.class, createSQL.toString()); + } + + @Override + public TableCreateBuilder addColumn(@NotNull String column) { + Objects.requireNonNull(column, "column could not be null"); + this.columns.add(column); + return this; + } + + @Override + public TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName, @Nullable NumberType numberType, + boolean asPrimaryKey, boolean unsigned) { + return addColumn(columnName, + (numberType == null ? NumberType.INT : numberType).name() + + (unsigned ? " UNSIGNED " : " ") + + "NOT NULL AUTO_INCREMENT " + (asPrimaryKey ? "PRIMARY KEY" : "UNIQUE KEY") + ); + } + + @Override + public TableCreateBuilder setIndex(@NotNull IndexType type, @Nullable String indexName, + @NotNull String columnName, @NotNull String... moreColumns) { + Objects.requireNonNull(columnName, "columnName could not be null"); + this.indexes.add(buildIndexSettings(type, indexName, columnName, moreColumns)); + return this; + } + + @Override + public TableCreateBuilder addForeignKey(@NotNull String tableColumn, @Nullable String constraintName, + @NotNull String foreignTable, @NotNull String foreignColumn, + @Nullable ForeignKeyRule updateRule, @Nullable ForeignKeyRule deleteRule) { + Objects.requireNonNull(tableColumn, "tableColumn could not be null"); + Objects.requireNonNull(foreignTable, "foreignTable could not be null"); + Objects.requireNonNull(foreignColumn, "foreignColumn could not be null"); + + StringBuilder keyBuilder = new StringBuilder(); + + keyBuilder.append("CONSTRAINT "); + if (constraintName == null) { + keyBuilder.append(withBackQuote("fk_" + tableColumn + "_" + foreignTable)); + } else { + keyBuilder.append(withBackQuote(constraintName)); + } + keyBuilder.append(" "); + keyBuilder.append("FOREIGN KEY (").append(withBackQuote(tableColumn)).append(") "); + keyBuilder.append("REFERENCES ").append(withBackQuote(foreignTable)).append("(").append(withBackQuote(foreignColumn)).append(")"); + + if (updateRule != null) keyBuilder.append(" ON UPDATE ").append(updateRule.getRuleName()); + if (deleteRule != null) keyBuilder.append(" ON DELETE ").append(deleteRule.getRuleName()); + + this.foreignKeys.add(keyBuilder.toString()); + return this; + } + + @Override + public TableCreateBuilder setColumns(@NotNull String... columns) { + Objects.requireNonNull(columns, "columns could not be null"); + this.columns = Arrays.asList(columns); + return this; + } + + @Override + public TableCreateBuilder setTableSettings(@NotNull String settings) { + Objects.requireNonNull(settings, "settings could not be null"); + this.tableSettings = settings; + return this; + } + + @Override + public TableCreateBuilder setTableComment(@Nullable String comment) { + this.tableComment = comment; + return this; + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableMetadataBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableMetadataBuilderImpl.java new file mode 100644 index 0000000..3436f54 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableMetadataBuilderImpl.java @@ -0,0 +1,74 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.api.builder.TableMetadataBuilder; +import com.io.yutian.aulib.sql.api.function.SQLBiFunction; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import com.io.yutian.aulib.sql.builder.AbstractSQLBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class TableMetadataBuilderImpl + extends AbstractSQLBuilder + implements TableMetadataBuilder { + + protected final @NotNull String tablePattern; + + public TableMetadataBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tablePattern) { + super(manager); + this.tablePattern = tablePattern; + } + + @Override + public CompletableFuture validateExist() { + return validate((meta, conn) -> meta.getTables(conn.getCatalog(), conn.getSchema(), tablePattern.toUpperCase(), new String[]{"TABLE"})); + } + + @Override + public CompletableFuture fetchColumns(@Nullable String columnPattern, + @NotNull SQLFunction reader) { + return getManager().fetchMetadata((meta, conn) -> meta.getColumns( + conn.getCatalog(), conn.getSchema(), tablePattern.toUpperCase(), + Optional.ofNullable(columnPattern).map(String::toUpperCase).orElse("%") + ), reader); + } + + @Override + public CompletableFuture isColumnExists(@NotNull String columnPattern) { + return validate((meta, conn) -> meta.getColumns( + conn.getCatalog(), conn.getSchema(), + tablePattern.toUpperCase(), columnPattern.toUpperCase() + )); + } + + @Override + public CompletableFuture> listColumns(@Nullable String columnPattern) { + return fetchColumns(columnPattern, (rs) -> { + Set data = new LinkedHashSet<>(); + while (rs.next()) { + data.add(rs.getString("COLUMN_NAME")); + } + return Collections.unmodifiableSet(data); + }); + } + + /** + * fast validate EXISTS. + * + * @param supplier supplier to get result set + * @return result future + */ + private CompletableFuture validate(SQLBiFunction supplier) { + return getManager().fetchMetadata(supplier, ResultSet::next); + } + +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableQueryBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableQueryBuilderImpl.java new file mode 100644 index 0000000..d230245 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/TableQueryBuilderImpl.java @@ -0,0 +1,94 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.query.PreparedQueryActionImpl; +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import com.io.yutian.aulib.sql.api.builder.TableQueryBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; + +public class TableQueryBuilderImpl + extends AbstractConditionalBuilder + implements TableQueryBuilder { + + + protected final @NotNull String tableName; + + String[] columns; + + @Nullable String orderBy; + + int[] pageLimit; + + public TableQueryBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + this.tableName = tableName; + } + + @Override + public PreparedQueryActionImpl build() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT").append(" "); + if (columns == null || columns.length < 1) { + sqlBuilder.append("*"); + } else { + for (int i = 0; i < columns.length; i++) { + String name = columns[i]; + sqlBuilder.append(withBackQuote(name)); + if (i != columns.length - 1) { + sqlBuilder.append(","); + } + } + } + + sqlBuilder.append(" ").append("FROM").append(" ").append(withBackQuote(tableName)); + + if (hasConditions()) sqlBuilder.append(" ").append(buildConditionSQL()); + + if (orderBy != null) sqlBuilder.append(" ").append(orderBy); + + if (pageLimit != null && pageLimit.length == 2) { + sqlBuilder.append(" LIMIT ").append(pageLimit[0]).append(",").append(pageLimit[1]); + } else if (limit > 0) { + sqlBuilder.append(" ").append(buildLimitSQL()); + } + + + return new PreparedQueryActionImpl(getManager(), sqlBuilder.toString()) + .setParams(hasConditionParams() ? getConditionParams() : null); + } + + @Override + public @NotNull String getTableName() { + return tableName; + } + + @Override + public TableQueryBuilderImpl selectColumns(@NotNull String... columnNames) { + Objects.requireNonNull(columnNames, "columnNames could not be null"); + this.columns = columnNames; + return this; + } + + @Override + public TableQueryBuilder orderBy(@NotNull String columnName, boolean asc) { + Objects.requireNonNull(columnName, "columnName could not be null"); + this.orderBy = "ORDER BY " + withBackQuote(columnName) + " " + (asc ? "ASC" : "DESC"); + return this; + } + + @Override + public TableQueryBuilder setPageLimit(int start, int end) { + this.pageLimit = new int[]{start, end}; + return this; + } + + @Override + protected TableQueryBuilderImpl getThis() { + return this; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/builder/impl/UpdateBuilderImpl.java b/src/main/java/com/io/yutian/aulib/sql/builder/impl/UpdateBuilderImpl.java new file mode 100644 index 0000000..2dfad8c --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/builder/impl/UpdateBuilderImpl.java @@ -0,0 +1,89 @@ +package com.io.yutian.aulib.sql.builder.impl; + +import com.io.yutian.aulib.sql.action.PreparedSQLUpdateActionImpl; +import com.io.yutian.aulib.sql.api.SQLAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.builder.UpdateBuilder; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static com.io.yutian.aulib.sql.api.SQLBuilder.withBackQuote; + +public class UpdateBuilderImpl + extends AbstractConditionalBuilder> + implements UpdateBuilder { + + protected final @NotNull String tableName; + + @NotNull LinkedHashMap columnData; + + public UpdateBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) { + super(manager); + this.tableName = tableName; + this.columnData = new LinkedHashMap<>(); + } + + @Override + public PreparedSQLUpdateAction build() { + + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("UPDATE ").append(withBackQuote(getTableName())).append(" SET "); + + Iterator iterator = this.columnData.keySet().iterator(); + while (iterator.hasNext()) { + sqlBuilder.append(withBackQuote(iterator.next())).append(" = ?"); + if (iterator.hasNext()) sqlBuilder.append(" , "); + } + List allParams = new ArrayList<>(this.columnData.values()); + + if (hasConditions()) { + sqlBuilder.append(" ").append(buildConditionSQL()); + allParams.addAll(getConditionParams()); + } + + if (limit > 0) sqlBuilder.append(" ").append(buildLimitSQL()); + + return new PreparedSQLUpdateActionImpl<>(getManager(), Integer.class, sqlBuilder.toString(), allParams); + } + + @Override + public @NotNull String getTableName() { + return tableName; + } + + @Override + public UpdateBuilder addColumnValue(@NotNull String columnName, Object columnValue) { + Objects.requireNonNull(columnName, "columnName could not be null"); + this.columnData.put(columnName, columnValue); + return this; + } + + @Override + public UpdateBuilder setColumnValues(LinkedHashMap columnData) { + this.columnData = columnData; + return this; + } + + @Override + public UpdateBuilder setColumnValues(@NotNull String[] columnNames, @Nullable Object[] columnValues) { + Objects.requireNonNull(columnNames, "columnName could not be null"); + if (columnNames.length != columnValues.length) { + throw new RuntimeException("columnNames are not match with columnValues"); + } + LinkedHashMap columnData = new LinkedHashMap<>(); + for (int i = 0; i < columnNames.length; i++) { + columnData.put(columnNames[i], columnValues[i]); + } + return setColumnValues(columnData); + } + + + @Override + protected UpdateBuilder getThis() { + return this; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/manager/SQLManagerImpl.java b/src/main/java/com/io/yutian/aulib/sql/manager/SQLManagerImpl.java new file mode 100644 index 0000000..59d62d6 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/manager/SQLManagerImpl.java @@ -0,0 +1,254 @@ +package com.io.yutian.aulib.sql.manager; + +import com.io.yutian.aulib.sql.action.PreparedSQLBatchUpdateActionImpl; +import com.io.yutian.aulib.sql.action.PreparedSQLUpdateActionImpl; +import com.io.yutian.aulib.sql.action.SQLUpdateActionImpl; +import com.io.yutian.aulib.sql.action.SQLUpdateBatchActionImpl; +import com.io.yutian.aulib.sql.api.SQLManager; +import com.io.yutian.aulib.sql.api.SQLQuery; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateAction; +import com.io.yutian.aulib.sql.api.action.PreparedSQLUpdateBatchAction; +import com.io.yutian.aulib.sql.api.action.SQLUpdateBatchAction; +import com.io.yutian.aulib.sql.api.builder.*; +import com.io.yutian.aulib.sql.api.function.SQLBiFunction; +import com.io.yutian.aulib.sql.api.function.SQLDebugHandler; +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLFunction; +import com.io.yutian.aulib.sql.builder.impl.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +public class SQLManagerImpl implements SQLManager { + + private final Logger LOGGER; + private final DataSource dataSource; + private final ConcurrentHashMap activeQuery = new ConcurrentHashMap<>(); + protected ExecutorService executorPool; + @NotNull Supplier debugMode = () -> Boolean.FALSE; + + @NotNull SQLExceptionHandler exceptionHandler; + @NotNull SQLDebugHandler debugHandler; + + public SQLManagerImpl(@NotNull DataSource dataSource) { + this(dataSource, null); + } + + public SQLManagerImpl(@NotNull DataSource dataSource, @Nullable String name) { + this(dataSource, LoggerFactory.getLogger(SQLManagerImpl.class), name); + } + + public SQLManagerImpl(@NotNull DataSource dataSource, @NotNull Logger logger, @Nullable String name) { + String managerName = "SQLManager" + (name != null ? "#" + name : ""); + this.LOGGER = logger; + this.dataSource = dataSource; + this.executorPool = SQLManager.defaultExecutorPool(managerName); + this.exceptionHandler = SQLExceptionHandler.detailed(getLogger()); + this.debugHandler = SQLDebugHandler.defaultHandler(getLogger()); + } + + @Override + public boolean isDebugMode() { + return this.debugMode.get(); + } + + @Override + public void setDebugMode(@NotNull Supplier<@NotNull Boolean> debugMode) { + this.debugMode = debugMode; + } + + @Override + public @NotNull SQLDebugHandler getDebugHandler() { + return this.debugHandler; + } + + @Override + public void setDebugHandler(@NotNull SQLDebugHandler debugHandler) { + this.debugHandler = debugHandler; + } + + @Override + public Logger getLogger() { + return LOGGER; + } + + public @NotNull ExecutorService getExecutorPool() { + return executorPool; + } + + public void setExecutorPool(@NotNull ExecutorService executorPool) { + this.executorPool = executorPool; + } + + @Override + public @NotNull DataSource getDataSource() { + return this.dataSource; + } + + @Override + public @NotNull Connection getConnection() throws SQLException { + return getDataSource().getConnection(); + } + + @Override + public @NotNull Map getActiveQuery() { + return this.activeQuery; + } + + @Override + public @NotNull SQLExceptionHandler getExceptionHandler() { + return this.exceptionHandler; + } + + @Override + public void setExceptionHandler(@Nullable SQLExceptionHandler handler) { + if (handler == null) this.exceptionHandler = SQLExceptionHandler.detailed(getLogger()); + else this.exceptionHandler = handler; + } + + @Override + public Integer executeSQL(String sql) { + return new SQLUpdateActionImpl<>(this, Integer.class, sql).execute(null); + } + + @Override + public Integer executeSQL(String sql, Object[] params) { + return new PreparedSQLUpdateActionImpl<>(this, Integer.class, sql, params).execute(null); + } + + @Override + public List executeSQLBatch(String sql, Iterable paramsBatch) { + return new PreparedSQLBatchUpdateActionImpl<>(this, Integer.class, sql).setAllParams(paramsBatch).execute(null); + } + + @Override + public List executeSQLBatch(@NotNull String sql, String... moreSQL) { + SQLUpdateBatchAction action = new SQLUpdateBatchActionImpl(this, sql); + if (moreSQL != null && moreSQL.length > 0) { + Arrays.stream(moreSQL).forEach(action::addBatch); + } + return action.execute(null); + } + + @Override + public @Nullable List executeSQLBatch(@NotNull Iterable sqlBatch) { + Iterator iterator = sqlBatch.iterator(); + if (!iterator.hasNext()) return null; // PLEASE GIVE IT SOMETHING + + SQLUpdateBatchAction action = new SQLUpdateBatchActionImpl(this, iterator.next()); + while (iterator.hasNext()) { + action.addBatch(iterator.next()); + } + + return action.execute(null); + } + + @Override + public CompletableFuture fetchMetadata(@NotNull SQLBiFunction reader) { + return CompletableFuture.supplyAsync(() -> { + try (Connection conn = getConnection()) { + return reader.apply(conn.getMetaData(), conn); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + }, this.executorPool); + } + + @Override + public CompletableFuture fetchMetadata(@NotNull SQLBiFunction supplier, + @NotNull SQLFunction<@NotNull ResultSet, R> reader) { + return fetchMetadata((meta, conn) -> { + try (ResultSet rs = supplier.apply(conn.getMetaData(), conn)) { + if (rs == null) throw new NullPointerException("Metadata返回的ResultSet为null。"); + else return reader.apply(rs); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + }); + } + + @Override + public TableCreateBuilder createTable(@NotNull String tableName) { + return new TableCreateBuilderImpl(this, tableName); + } + + @Override + public TableAlterBuilder alterTable(@NotNull String tableName) { + return new TableAlterBuilderImpl(this, tableName); + } + + @Override + public TableMetadataBuilder fetchTableMetadata(@NotNull String tablePattern) { + return new TableMetadataBuilderImpl(this, tablePattern); + } + + @Override + public QueryBuilder createQuery() { + return new QueryBuilderImpl(this); + } + + @Override + public InsertBuilder> createInsertBatch(@NotNull String tableName) { + return new InsertBuilderImpl>(this, tableName) { + @Override + public PreparedSQLUpdateBatchAction setColumnNames(List columnNames) { + return new PreparedSQLBatchUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames)); + } + }; + } + + @Override + public InsertBuilder> createInsert(@NotNull String tableName) { + return new InsertBuilderImpl>(this, tableName) { + @Override + public PreparedSQLUpdateAction setColumnNames(List columnNames) { + return new PreparedSQLUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames)); + } + }; + } + + @Override + public ReplaceBuilder> createReplaceBatch(@NotNull String tableName) { + return new ReplaceBuilderImpl>(this, tableName) { + @Override + public PreparedSQLUpdateBatchAction setColumnNames(List columnNames) { + return new PreparedSQLBatchUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames)); + } + }; + } + + @Override + public ReplaceBuilder> createReplace(@NotNull String tableName) { + return new ReplaceBuilderImpl>(this, tableName) { + @Override + public PreparedSQLUpdateAction setColumnNames(List columnNames) { + return new PreparedSQLUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames)); + } + }; + } + + @Override + public UpdateBuilder createUpdate(@NotNull String tableName) { + return new UpdateBuilderImpl(this, tableName); + } + + @Override + public DeleteBuilder createDelete(@NotNull String tableName) { + return new DeleteBuilderImpl(this, tableName); + } + + +} + diff --git a/src/main/java/com/io/yutian/aulib/sql/query/PreparedQueryActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/query/PreparedQueryActionImpl.java new file mode 100644 index 0000000..76e35cd --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/query/PreparedQueryActionImpl.java @@ -0,0 +1,83 @@ +package com.io.yutian.aulib.sql.query; + +import com.io.yutian.aulib.sql.api.action.query.PreparedQueryAction; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import com.io.yutian.aulib.sql.util.StatementUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class PreparedQueryActionImpl extends QueryActionImpl implements PreparedQueryAction { + + Consumer handler; + Object[] params; + + public PreparedQueryActionImpl(@NotNull SQLManagerImpl manager, @NotNull String sql) { + super(manager, sql); + } + + @Override + public PreparedQueryActionImpl setParams(@Nullable Object... params) { + this.params = params; + return this; + } + + @Override + public PreparedQueryActionImpl setParams(@Nullable Iterable params) { + if (params == null) { + return setParams((Object[]) null); + } else { + List paramsList = new ArrayList<>(); + params.forEach(paramsList::add); + return setParams(paramsList.toArray()); + } + } + + @Override + public PreparedQueryActionImpl handleStatement(@Nullable Consumer statement) { + this.handler = statement; + return this; + } + + + @Override + public @NotNull SQLQueryImpl execute() throws SQLException { + debugMessage(Collections.singletonList(params)); + + Connection connection = getManager().getConnection(); + PreparedStatement preparedStatement; + try { + if (handler == null) { + preparedStatement = StatementUtil.createPrepareStatement(connection, getSQLContent(), this.params); + } else { + preparedStatement = connection.prepareStatement(getSQLContent()); + handler.accept(preparedStatement); + } + } catch (SQLException exception) { + connection.close(); + throw exception; + } + + try { + SQLQueryImpl query = new SQLQueryImpl( + getManager(), this, + connection, preparedStatement, + preparedStatement.executeQuery() + ); + getManager().getActiveQuery().put(getActionUUID(), query); + return query; + } catch (SQLException exception) { + preparedStatement.close(); + connection.close(); + throw exception; + } + + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/query/QueryActionImpl.java b/src/main/java/com/io/yutian/aulib/sql/query/QueryActionImpl.java new file mode 100644 index 0000000..28aff1b --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/query/QueryActionImpl.java @@ -0,0 +1,63 @@ +package com.io.yutian.aulib.sql.query; + +import com.io.yutian.aulib.sql.action.AbstractSQLAction; +import com.io.yutian.aulib.sql.api.SQLQuery; +import com.io.yutian.aulib.sql.api.action.query.QueryAction; +import com.io.yutian.aulib.sql.api.function.SQLExceptionHandler; +import com.io.yutian.aulib.sql.api.function.SQLHandler; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +public class QueryActionImpl extends AbstractSQLAction implements QueryAction { + + public QueryActionImpl(@NotNull SQLManagerImpl manager, @NotNull String sql) { + super(manager, sql); + } + + @Override + public @NotNull SQLQueryImpl execute() throws SQLException { + debugMessage(new ArrayList<>()); + + Connection connection = getManager().getConnection(); + Statement statement; + + try { + statement = connection.createStatement(); + } catch (SQLException ex) { + connection.close(); + throw ex; + } + + try { + SQLQueryImpl query = new SQLQueryImpl( + getManager(), this, + connection, statement, + statement.executeQuery(getSQLContent()) + ); + getManager().getActiveQuery().put(getActionUUID(), query); + + return query; + } catch (SQLException exception) { + statement.close(); + connection.close(); + throw exception; + } + } + + + @Override + public void executeAsync(SQLHandler success, SQLExceptionHandler failure) { + getManager().getExecutorPool().submit(() -> { + try (SQLQueryImpl query = execute()) { + if (success != null) success.accept(query); + } catch (SQLException exception) { + handleException(failure, exception); + } + }); + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/query/SQLQueryImpl.java b/src/main/java/com/io/yutian/aulib/sql/query/SQLQueryImpl.java new file mode 100644 index 0000000..ee05a6d --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/query/SQLQueryImpl.java @@ -0,0 +1,97 @@ +package com.io.yutian.aulib.sql.query; + +import com.io.yutian.aulib.sql.api.SQLQuery; +import com.io.yutian.aulib.sql.manager.SQLManagerImpl; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +public class SQLQueryImpl implements SQLQuery { + + protected final long executeTime; + + protected final SQLManagerImpl sqlManager; + protected final Connection connection; + protected final Statement statement; + protected final ResultSet resultSet; + protected QueryActionImpl queryAction; + + public SQLQueryImpl( + SQLManagerImpl sqlManager, QueryActionImpl queryAction, + Connection connection, Statement statement, ResultSet resultSet + ) { + this(sqlManager, queryAction, connection, statement, resultSet, System.nanoTime()); + } + + public SQLQueryImpl( + SQLManagerImpl sqlManager, QueryActionImpl queryAction, + Connection connection, Statement statement, ResultSet resultSet, + long executeTime + ) { + this.executeTime = executeTime; + this.sqlManager = sqlManager; + this.queryAction = queryAction; + this.connection = connection; + this.statement = statement; + this.resultSet = resultSet; + } + + @Override + public long getExecuteTime(TimeUnit timeUnit) { + return timeUnit.convert(this.executeTime, TimeUnit.NANOSECONDS); + } + + @Override + public SQLManagerImpl getManager() { + return this.sqlManager; + } + + @Override + public QueryActionImpl getAction() { + return this.queryAction; + } + + @Override + public ResultSet getResultSet() { + return this.resultSet; + } + + @Override + public String getSQLContent() { + return getAction().getSQLContent(); + } + + @Override + public void close() { + try { + if (getResultSet() != null && !getResultSet().isClosed()) getResultSet().close(); + if (getStatement() != null && !getStatement().isClosed()) getStatement().close(); + if (getConnection() != null && !getConnection().isClosed()) getConnection().close(); + + if (getManager().isDebugMode()) { + try { + getManager().getDebugHandler().afterQuery(this, getExecuteTime(TimeUnit.NANOSECONDS), System.nanoTime()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + getManager().getActiveQuery().remove(getAction().getActionUUID()); + } catch (SQLException e) { + getAction().handleException(getAction().defaultExceptionHandler(), e); + } + this.queryAction = null; + } + + @Override + public Statement getStatement() { + return this.statement; + } + + @Override + public Connection getConnection() { + return this.connection; + } +} diff --git a/src/main/java/com/io/yutian/aulib/sql/util/StatementUtil.java b/src/main/java/com/io/yutian/aulib/sql/util/StatementUtil.java new file mode 100644 index 0000000..43ccee5 --- /dev/null +++ b/src/main/java/com/io/yutian/aulib/sql/util/StatementUtil.java @@ -0,0 +1,210 @@ +package com.io.yutian.aulib.sql.util; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class StatementUtil { + + /** + * 创建一个 {@link PreparedStatement} 。 + * + * @param connection 数据库连接 + * @param sql SQL语句,使用"?"做为占位符 + * @param params "?"所代表的对应参数列表 + * @return 完成参数填充的 {@link PreparedStatement} + */ + public static PreparedStatement createPrepareStatement( + Connection connection, String sql, Object[] params + ) throws SQLException { + return createPrepareStatement(connection, sql, params, false); + } + + /** + * 创建一个 {@link PreparedStatement} 。 + * + * @param connection 数据库连接 + * @param sql SQL语句,使用"?"做为占位符 + * @param params "?"所代表的对应参数列表 + * @param returnGeneratedKey 是否会返回自增主键 + * @return 完成参数填充的 {@link PreparedStatement} + */ + public static PreparedStatement createPrepareStatement( + Connection connection, String sql, Object[] params, boolean returnGeneratedKey + ) throws SQLException { + sql = sql.trim(); + PreparedStatement statement = connection.prepareStatement(sql, returnGeneratedKey ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS); + Map nullTypeMap = new HashMap<>(); + if (params != null) fillParams(statement, Arrays.asList(params), nullTypeMap); + statement.addBatch(); + return statement; + } + + /** + * 创建批量操作的一个 {@link PreparedStatement} 。 + * + * @param connection 数据库连接 + * @param sql SQL语句,使用"?"做为占位符 + * @param paramsBatch "?"所代表的对应参数列表 + * @return 完成参数填充的 {@link PreparedStatement} + */ + public static PreparedStatement createPrepareStatementBatch( + Connection connection, String sql, Iterable paramsBatch + ) throws SQLException { + return createPrepareStatementBatch(connection, sql, paramsBatch, false); + } + + /** + * 创建批量操作的一个 {@link PreparedStatement} 。 + * + * @param connection 数据库连接 + * @param sql SQL语句,使用"?"做为占位符 + * @param paramsBatch "?"所代表的对应参数列表 + * @param returnGeneratedKey 是否会返回自增主键 + * @return 完成参数填充的 {@link PreparedStatement} + */ + public static PreparedStatement createPrepareStatementBatch( + Connection connection, String sql, Iterable paramsBatch, boolean returnGeneratedKey + ) throws SQLException { + + sql = sql.trim(); + PreparedStatement statement = connection.prepareStatement(sql, returnGeneratedKey ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS); + Map nullTypeMap = new HashMap<>(); + for (Object[] params : paramsBatch) { + fillParams(statement, Arrays.asList(params), nullTypeMap); + statement.addBatch(); + } + + return statement; + } + + + /** + * 填充PreparedStatement的参数。 + * + * @param statement PreparedStatement + * @param params SQL参数 + * @return {@link PreparedStatement} 填充参数后的PreparedStatement + * @throws SQLException SQL执行异常 + */ + public static PreparedStatement fillParams( + PreparedStatement statement, Iterable params + ) throws SQLException { + return fillParams(statement, params, null); + } + + /** + * 填充PreparedStatement的参数。 + * + * @param statement PreparedStatement + * @param params SQL参数 + * @param nullCache null参数的类型缓存,避免循环中重复获取类型 + * @return 完成参数填充的 {@link PreparedStatement} + */ + public static PreparedStatement fillParams( + PreparedStatement statement, Iterable params, Map nullCache + ) throws SQLException { + if (null == params) { + return statement;// 无参数 + } + + int paramIndex = 1;//第一个参数从1计数 + for (Object param : params) { + setParam(statement, paramIndex++, param, nullCache); + } + return statement; + } + + /** + * 获取null字段对应位置的数据类型 + * 如果类型获取失败将使用默认的 {@link Types#VARCHAR} + * + * @param statement {@link PreparedStatement} + * @param paramIndex 参数序列,第一位从1开始 + * @return 数据类型,默认为 {@link Types#VARCHAR} + */ + public static int getNullType(PreparedStatement statement, int paramIndex) { + try { + ParameterMetaData pmd = statement.getParameterMetaData(); + return pmd.getParameterType(paramIndex); + } catch (SQLException ignore) { + return Types.VARCHAR; + } + } + + /** + * 为 {@link PreparedStatement} 设置单个参数 + * + * @param preparedStatement {@link PreparedStatement} + * @param paramIndex 参数序列,从1开始 + * @param param 参数,不能为{@code null} + * @param nullCache 用于缓存参数为null位置的类型,避免重复获取 + */ + private static void setParam( + PreparedStatement preparedStatement, int paramIndex, Object param, + Map nullCache + ) throws SQLException { + + if (param == null) { + Integer type = (null == nullCache) ? null : nullCache.get(paramIndex); + if (null == type) { + type = getNullType(preparedStatement, paramIndex); + if (null != nullCache) { + nullCache.put(paramIndex, type); + } + } + preparedStatement.setNull(paramIndex, type); + } + + // 针对UUID特殊处理,避免元数据直接传入 + if (param instanceof UUID) { + preparedStatement.setString(paramIndex, param.toString()); + return; + } + + // 针对StringBuilder或StringBuffer进行处理,避免元数据传入 + if (param instanceof StringBuilder || param instanceof StringBuffer) { + preparedStatement.setString(paramIndex, param.toString()); + return; + } + + // 日期特殊处理,默认按照时间戳传入,避免精度丢失 + if (param instanceof java.util.Date) { + if (param instanceof Date) { + preparedStatement.setDate(paramIndex, (Date) param); + } else if (param instanceof Time) { + preparedStatement.setTime(paramIndex, (Time) param); + } else { + preparedStatement.setTimestamp(paramIndex, new Timestamp(((java.util.Date) param).getTime())); + } + return; + } + + // 针对大数字类型的特殊处理 + if (param instanceof Number) { + if (param instanceof BigDecimal) { + // BigDecimal的转换交给JDBC驱动处理 + preparedStatement.setBigDecimal(paramIndex, (BigDecimal) param); + return; + } else if (param instanceof BigInteger) { + preparedStatement.setBigDecimal(paramIndex, new BigDecimal((BigInteger) param)); + return; + } + // 其它数字类型按照默认类型传入 + } + + if (param instanceof Enum) { + //枚举类采用 name() + preparedStatement.setString(paramIndex, ((Enum) param).name()); + return; + } + + // 其它参数类型直接传入 + preparedStatement.setObject(paramIndex, param); + } + +}