This commit is contained in:
YuTian 2024-07-16 17:03:43 +08:00
parent 8b5a40bad1
commit f2ed245af0
55 changed files with 4319 additions and 5 deletions

16
pom.xml
View File

@ -48,31 +48,30 @@
<version>1.18.2-R0.1-SNAPSHOT</version> <version>1.18.2-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.io.yutian.pixelpaper</groupId>
<artifactId>pixelpaper-api</artifactId>
<version>1.18.2</version>
</dependency>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId> <artifactId>spigot</artifactId>
<version>1.18.2</version> <version>1.18.2</version>
<classifier>nms</classifier> <classifier>nms</classifier>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>com.mojang</groupId>
<artifactId>authlib</artifactId> <artifactId>authlib</artifactId>
<version>3.3.39</version> <version>3.3.39</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>com.mojang</groupId>
<artifactId>brigadier</artifactId> <artifactId>brigadier</artifactId>
<version>1.0.18</version> <version>1.0.18</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>com.mojang</groupId>
<artifactId>datafixerupper</artifactId> <artifactId>datafixerupper</artifactId>
<version>4.1.27</version> <version>4.1.27</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@ -84,6 +83,13 @@
<artifactId>zstd-jni</artifactId> <artifactId>zstd-jni</artifactId>
<version>1.5.6-3</version> <version>1.5.6-3</version>
</dependency> </dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -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<T> implements SQLAction<T> {
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<Object[]> params) {
if (getManager().isDebugMode()) {
try {
getManager().getDebugHandler().beforeExecute(this, params);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
@Override
@SuppressWarnings("FutureReturnValueIgnored")
public void executeAsync(SQLHandler<T> 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 <R> CompletableFuture<R> executeFuture(@NotNull SQLFunction<T, R> handler) {
CompletableFuture<R> future = new CompletableFuture<>();
executeAsync((t -> future.complete(handler.apply(t))), (e, q) -> future.completeExceptionally(e));
return future;
}
}

View File

@ -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<T extends Number>
extends AbstractSQLAction<List<T>>
implements PreparedSQLUpdateBatchAction<T> {
boolean returnKeys = false;
@NotNull List<Object[]> allParams = new ArrayList<>();
protected final @NotNull Class<T> numberClass;
public PreparedSQLBatchUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull String sql) {
super(manager, sql);
this.numberClass = numberClass;
this.allParams = new ArrayList<>();
}
public PreparedSQLBatchUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull UUID uuid, @NotNull String sql) {
super(manager, sql, uuid);
this.numberClass = numberClass;
}
@Override
public PreparedSQLBatchUpdateActionImpl<T> setAllParams(Iterable<Object[]> allParams) {
List<Object[]> paramsList = new ArrayList<>();
allParams.forEach(paramsList::add);
this.allParams = paramsList;
return this;
}
@Override
public PreparedSQLBatchUpdateActionImpl<T> addParamsBatch(Object... params) {
this.allParams.add(params);
return this;
}
@Override
public PreparedSQLBatchUpdateActionImpl<T> returnGeneratedKeys() {
this.returnKeys = true;
return this;
}
@Override
public <N extends Number> PreparedSQLBatchUpdateActionImpl<N> returnGeneratedKeys(Class<N> keyTypeClass) {
return new PreparedSQLBatchUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent())
.setAllParams(allParams).returnGeneratedKeys();
}
@Override
public @NotNull List<T> 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<T> generatedKeys = new ArrayList<>();
while (resultSet.next()) {
generatedKeys.add(resultSet.getObject(1, numberClass));
}
return generatedKeys;
}
}
}
}
}
}

View File

@ -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<T extends Number>
extends SQLUpdateActionImpl<T>
implements PreparedSQLUpdateAction<T> {
Object[] params;
public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull String sql) {
this(manager, numberClass, sql, (Object[]) null);
}
public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull String sql, @Nullable List<Object> params) {
this(manager, numberClass, sql, params == null ? null : params.toArray());
}
public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull String sql, @Nullable Object[] params) {
super(manager, numberClass, sql);
this.params = params;
}
public PreparedSQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull UUID uuid, @NotNull String sql,
Object[] params) {
super(manager, numberClass, uuid, sql);
this.params = params;
}
@Override
public PreparedSQLUpdateActionImpl<T> setParams(Object... params) {
this.params = params;
return this;
}
@Override
public PreparedSQLUpdateActionImpl<T> setParams(@Nullable Iterable<Object> params) {
if (params == null) {
return setParams((Object[]) null);
} else {
List<Object> 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 <N extends Number> SQLUpdateAction<N> returnGeneratedKey(Class<N> keyTypeClass) {
PreparedSQLUpdateActionImpl<N> newAction = new PreparedSQLUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent(), params);
newAction.returnGeneratedKey();
return newAction;
}
}

View File

@ -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<T extends Number>
extends AbstractSQLAction<T>
implements SQLUpdateAction<T> {
protected final @NotNull Class<T> numberClass;
protected boolean returnGeneratedKeys = false;
public SQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> numberClass,
@NotNull String sql) {
super(manager, sql);
this.numberClass = numberClass;
}
public SQLUpdateActionImpl(@NotNull SQLManagerImpl manager, @NotNull Class<T> 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<T> returnGeneratedKey() {
this.returnGeneratedKeys = true;
return this;
}
@Override
public <N extends Number> SQLUpdateAction<N> returnGeneratedKey(Class<N> keyTypeClass) {
return new SQLUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent()).returnGeneratedKey();
}
}

View File

@ -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<List<Integer>>
implements SQLUpdateBatchAction {
protected final List<String> sqlContents = new ArrayList<>();
public SQLUpdateBatchActionImpl(@NotNull SQLManagerImpl manager, @NotNull String sql) {
super(manager, sql);
this.sqlContents.add(sql);
}
@Override
public @NotNull List<String> 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<Integer> 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());
}
}
}
}

View File

@ -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语句并进行处理返回的基本类
*
* <ul>
* <li>同步执行 {@link #execute()}, {@link #execute(SQLFunction, SQLExceptionHandler)}
* <br>同步执行方法中有会抛出异常的方法与不抛出异常的方法
* <br>若选择不抛出异常则返回值可能为空需要特殊处理</li>
*
* <li>异步执行 {@link #executeAsync(SQLHandler, SQLExceptionHandler)}
* <br>异步执行时将提供成功与异常两种处理方式
* <br>可自行选择是否对数据或异常进行处理
* <br>默认的异常处理器为 {@link #defaultExceptionHandler()}
* <br>若有特殊需要可通过{@link #setExceptionHandler(SQLExceptionHandler)} 方法修改默认的处理器</li>
* </ul>
*
* @param <T> 需要返回的类型
* @author CarmJos
* @since 0.0.1
*/
public interface SQLAction<T> {
/**
* 得到该Action的UUID
*
* @return UUID
*/
@NotNull UUID getActionUUID();
/**
* 得到短八位格式的UUID
*
* @return UUID(8)
*/
@NotNull String getShortID();
/**
* 得到该Action的创建时间
* <br>注意此处获得的时间非时间戳毫秒数仅用于计算耗时
*
* @return 创建时间 (毫秒)
*/
default long getCreateTime() {
return getCreateTime(TimeUnit.MILLISECONDS);
}
/**
* 得到该Action的创建时间
* <br>注意此处获得的时间非时间戳毫秒数仅用于计算耗时
*
* @param unit 时间单位
* @return 创建时间
*/
long getCreateTime(TimeUnit unit);
/**
* 得到该Action所要执行的源SQL语句
*
* @return 源SQL语句
*/
@NotNull String getSQLContent();
/**
* 得到该Action所要执行的源SQL语句列表
*
* @return 源SQL语句列表
*/
default @NotNull List<String> 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 <R> 需要返回的内容
* @return 指定类型数据
*/
@Nullable
default <R> R execute(@NotNull SQLFunction<T, R> function,
@Nullable SQLExceptionHandler exceptionHandler) {
return execute(function, null, exceptionHandler);
}
/**
* 执行语句并处理返回值
*
* @param function 处理方法
* @param defaultResult 默认结果若处理后的结果为null则返回该值
* @param exceptionHandler 异常处理器 默认为 {@link #defaultExceptionHandler()}
* @param <R> 需要返回的内容
* @return 指定类型数据
*/
@Nullable
@Contract("_,!null,_ -> !null")
default <R> R execute(@NotNull SQLFunction<T, R> function,
@Nullable R defaultResult,
@Nullable SQLExceptionHandler exceptionHandler) {
try {
return executeFunction(function, defaultResult);
} catch (SQLException exception) {
handleException(exceptionHandler, exception);
return null;
}
}
/**
* 执行语句并处理返回值
*
* @param function 处理方法
* @param <R> 需要返回的内容
* @return 指定类型数据
* @throws SQLException 当SQL操作出现问题时抛出
*/
@Nullable
default <R> R executeFunction(@NotNull SQLFunction<@NotNull T, R> function) throws SQLException {
return executeFunction(function, null);
}
/**
* 执行语句并处理返回值
*
* @param function 处理方法
* @param defaultResult 默认结果若处理后的结果为null则返回该值
* @param <R> 需要返回的内容
* @return 指定类型数据
* @throws SQLException 当SQL操作出现问题时抛出
*/
@Nullable
@Contract("_,!null -> !null")
default <R> 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<T> success) {
executeAsync(success, null);
}
/**
* 异步执行SQL语句
*
* @param success 成功时的操作
* @param failure 异常处理器 默认为 {@link SQLAction#defaultExceptionHandler()}
*/
void executeAsync(@Nullable SQLHandler<T> success,
@Nullable SQLExceptionHandler failure);
/**
* 以异步Future方式执行SQL语句
*
* @return 异步执行的Future实例可通过 {@link Future#get()} 阻塞并等待结果
*/
default @NotNull CompletableFuture<Void> executeFuture() {
return executeFuture((t -> null));
}
/**
* 以异步Future方式执行SQL语句
*
* @return 异步执行的Future实例可通过 {@link Future#get()} 阻塞并等待结果
*/
<R> @NotNull CompletableFuture<R> executeFuture(@NotNull SQLFunction<T, R> 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();
}
/**
* 设定通用的异常处理器
* <br> 在使用 {@link #execute(SQLExceptionHandler)} 等相关方法时若传入的处理器为null则会采用此处理器
* <br> 若该方法传入参数为 null则会使用 {@link #defaultExceptionHandler()}
*
* @param handler 异常处理器
*/
default void setExceptionHandler(@Nullable SQLExceptionHandler handler) {
getManager().setExceptionHandler(handler);
}
}

View File

@ -0,0 +1,31 @@
package com.io.yutian.aulib.sql.api;
import org.jetbrains.annotations.NotNull;
/**
* SQLBuilder 是用于构建SQL语句以生成SQLAction执行操作的中间类
* <br>其连接了{@link SQLManager} {@link SQLAction} ,避免大量的代码堆积
* <br>也是本接口的核心功能所在
*
* @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();
}

View File

@ -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()} 的线程池
* <br> 默认线程池为 {@link #defaultExecutorPool(String)}
*
* @return {@link ExecutorService}
*/
@NotNull ExecutorService getExecutorPool();
/**
* 设定用于执行 {@link SQLAction#executeAsync()} 的线程池.
* <br> 默认线程池为 {@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<UUID, SQLQuery> getActiveQuery();
/**
* 获取改管理器提供的默认异常处理器
* 若未使用过 {@link #setExceptionHandler(SQLExceptionHandler)} 方法
* 则默认返回 {@link SQLExceptionHandler#detailed(Logger)}
*
* @return {@link SQLExceptionHandler}
*/
@NotNull SQLExceptionHandler getExceptionHandler();
/**
* 设定通用的异常处理器
* <br> 在使用 {@link SQLAction#execute(SQLExceptionHandler)} 等相关方法时若传入的处理器为null则会采用此处理器
* <br> 若该方法传入参数为 null则会使用 {@link SQLExceptionHandler#detailed(Logger)}
*
* @param handler 异常处理器
*/
void setExceptionHandler(@Nullable SQLExceptionHandler handler);
/**
* 执行一条不需要返回结果的SQL语句(多用于UPDATEREPLACEDELETE方法)
* 该方法使用 Statement 实现请注意SQL注入风险
*
* @param sql SQL语句内容
* @return 更新的行数
* @see SQLUpdateAction
*/
@Nullable Integer executeSQL(String sql);
/**
* 执行一条不需要返回结果的预处理SQL更改(UPDATEREPLACEDELETE)
*
* @param sql SQL语句内容
* @param params SQL语句中 ? 的对应参数
* @return 更新的行数
* @see PreparedSQLUpdateAction
*/
@Nullable Integer executeSQL(String sql, Object[] params);
/**
* 执行多条不需要返回结果的SQL更改(UPDATEREPLACEDELETE)
*
* @param sql SQL语句内容
* @param paramsBatch SQL语句中对应?的参数组
* @return 对应参数返回的行数
* @see PreparedSQLUpdateBatchAction
*/
@Nullable List<Integer> executeSQLBatch(String sql, Iterable<Object[]> paramsBatch);
/**
* 执行多条不需要返回结果的SQL
* 该方法使用 Statement 实现请注意SQL注入风险
*
* @param sql SQL语句内容
* @param moreSQL 更多SQL语句内容
* @return 对应参数返回的行数
* @see SQLUpdateBatchAction
*/
@Nullable List<Integer> executeSQLBatch(@NotNull String sql, String... moreSQL);
/**
* 执行多条不需要返回结果的SQL
*
* @param sqlBatch SQL语句内容
* @return 对应参数返回的行数
*/
@Nullable List<Integer> executeSQLBatch(@NotNull Iterable<String> sqlBatch);
/**
* 获取并操作 {@link DatabaseMetaData} 以得到需要的数据库消息
*
* @param reader 操作与读取的方法
* @param <R> 最终结果的返回类型
* @return 最终结果通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回
*/
default <R> CompletableFuture<R> fetchMetadata(@NotNull SQLFunction<DatabaseMetaData, R> reader) {
return fetchMetadata((meta, conn) -> reader.apply(meta));
}
/**
* 获取并操作 {@link DatabaseMetaData} 提供的指定 {@link ResultSet} 以得到需要的数据库消息
* <br> 该方法会自动关闭 {@link ResultSet}
*
* @param supplier 操作 {@link DatabaseMetaData} 以提供信息所在的 {@link ResultSet}
* @param reader 读取 {@link ResultSet} 中指定信息的方法
* @param <R> 最终结果的返回类型
* @return 最终结果通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回
* @throws NullPointerException supplier 提供的 {@link ResultSet} 为NULL时抛出
*/
default <R> CompletableFuture<R> fetchMetadata(@NotNull SQLFunction<DatabaseMetaData, ResultSet> supplier,
@NotNull SQLFunction<@NotNull ResultSet, R> reader) {
return fetchMetadata((meta, conn) -> supplier.apply(meta), reader);
}
/**
* 获取并操作 {@link DatabaseMetaData} 以得到需要的数据库消息
*
* @param reader 操作与读取的方法
* @param <R> 最终结果的返回类型
* @return 最终结果通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回
*/
<R> CompletableFuture<R> fetchMetadata(@NotNull SQLBiFunction<DatabaseMetaData, Connection, R> reader);
/**
* 获取并操作 {@link DatabaseMetaData} 提供的指定 {@link ResultSet} 以得到需要的数据库消息
* <br> 该方法会自动关闭 {@link ResultSet}
*
* @param supplier 操作 {@link DatabaseMetaData} 以提供信息所在的 {@link ResultSet}
* @param reader 读取 {@link ResultSet} 中指定信息的方法
* @param <R> 最终结果的返回类型
* @return 最终结果通过 {@link CompletableFuture#get()} 可阻塞并等待结果返回
* @throws NullPointerException supplier 提供的 {@link ResultSet} 为NULL时抛出
*/
<R> CompletableFuture<R> fetchMetadata(@NotNull SQLBiFunction<DatabaseMetaData, Connection, ResultSet> 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);
/**
* 快速获取表的部分元数据
* <br> 当需要获取其他元数据时请使用 {@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<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull String tableName);
/**
* 创建支持多组数据的插入操作
*
* @param tableName 目标表名
* @return {@link InsertBuilder}
*/
InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> createInsertBatch(@NotNull String tableName);
/**
* 创建一条替换操作
*
* @param tableName 目标表名
* @return {@link ReplaceBuilder}
*/
ReplaceBuilder<PreparedSQLUpdateAction<Integer>> createReplace(@NotNull String tableName);
/**
* 创建支持多组数据的替换操作
*
* @param tableName 目标表名
* @return {@link ReplaceBuilder}
*/
ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> createReplaceBatch(@NotNull String tableName);
/**
* 创建更新操作
*
* @param tableName 目标表名
* @return {@link UpdateBuilder}
*/
UpdateBuilder createUpdate(@NotNull String tableName);
/**
* 创建删除操作
*
* @param tableName 目标表名
* @return {@link DeleteBuilder}
*/
DeleteBuilder createDelete(@NotNull String tableName);
}

View File

@ -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 {
/**
* 获取该查询创建的时间
* <br>注意此处获得的时间非时间戳毫秒数仅用于计算耗时
*
* @return 创建时间
*/
default long getExecuteTime() {
return getExecuteTime(TimeUnit.MILLISECONDS);
}
/**
* 获取该查询创建的时间
* <br>注意此处获得的时间非时间戳毫秒数仅用于计算耗时
*
* @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();
}

View File

@ -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} 构建表用于快速创建与该表相关的操作
* <ul>
* <li>1. 调用 {@link NamedSQLTable#of(String, String[])} 方法创建一个 SQLTable 对象;</li>
* <li>2. 在应用初始化阶段调用 {@link NamedSQLTable#create(SQLManager)} 方法初始化 SQLTable 对象;</li>
* <li>3. 获取已创建的{@link NamedSQLTable} 实例直接调用对应方法进行关于表的相关操作</li>
* </ul>
*
* @author CarmJos
* @since 0.3.10
*/
public interface SQLTable {
static @NotNull NamedSQLTable of(@NotNull String tableName, @Nullable SQLHandler<TableCreateBuilder> 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<PreparedSQLUpdateAction<Integer>> createInsert() {
return Optional.ofNullable(getSQLManager()).map(this::createInsert)
.orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger."));
}
default @NotNull InsertBuilder<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull SQLManager sqlManager) {
return sqlManager.createInsert(getTableName());
}
default @NotNull InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> createInsertBatch() {
return Optional.ofNullable(getSQLManager()).map(this::createInsertBatch)
.orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger."));
}
default @NotNull InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> createInsertBatch(@NotNull SQLManager sqlManager) {
return sqlManager.createInsertBatch(getTableName());
}
default @NotNull ReplaceBuilder<PreparedSQLUpdateAction<Integer>> createReplace() {
return Optional.ofNullable(getSQLManager()).map(this::createReplace)
.orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger."));
}
default @NotNull ReplaceBuilder<PreparedSQLUpdateAction<Integer>> createReplace(@NotNull SQLManager sqlManager) {
return sqlManager.createReplace(getTableName());
}
default @NotNull ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> createReplaceBatch() {
return Optional.ofNullable(getSQLManager()).map(this::createReplaceBatch)
.orElseThrow(() -> new NullPointerException("This table doesn't have a SQLManger."));
}
default @NotNull ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> 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());
}
}

View File

@ -0,0 +1,24 @@
package com.io.yutian.aulib.sql.api.action;
import org.jetbrains.annotations.Nullable;
public interface PreparedSQLUpdateAction<T extends Number> extends SQLUpdateAction<T> {
/**
* 设定SQL语句中所有 ? 对应的参数
*
* @param params 参数内容
* @return {@link PreparedSQLUpdateAction}
*/
PreparedSQLUpdateAction<T> setParams(Object... params);
/**
* 设定SQL语句中所有 ? 对应的参数
*
* @param params 参数内容
* @return {@link PreparedSQLUpdateAction}
* @since 0.4.0
*/
PreparedSQLUpdateAction<T> setParams(@Nullable Iterable<Object> params);
}

View File

@ -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<T extends Number> extends SQLAction<List<T>> {
/**
* 设定多组SQL语句中所有 ? 对应的参数
*
* @param allParams 所有参数内容
* @return {@link PreparedSQLUpdateBatchAction}
*/
PreparedSQLUpdateBatchAction<T> setAllParams(Iterable<Object[]> allParams);
/**
* 添加一组SQL语句中所有 ? 对应的参数
*
* @param params 参数内容
* @return {@link PreparedSQLUpdateBatchAction}
*/
PreparedSQLUpdateBatchAction<T> addParamsBatch(Object... params);
/**
* 设定该操作返回自增键序列
*
* @return {@link SQLUpdateAction}
*/
PreparedSQLUpdateBatchAction<T> returnGeneratedKeys();
/**
* 设定该操作返回自增键序列
*
* @param keyTypeClass 自增序列的数字类型
* @param <N> 自增键序列类型 {@link Number}
* @return {@link SQLUpdateAction}
* @since 0.4.0
*/
<N extends Number> PreparedSQLUpdateBatchAction<N> returnGeneratedKeys(Class<N> keyTypeClass);
}

View File

@ -0,0 +1,26 @@
package com.io.yutian.aulib.sql.api.action;
import com.io.yutian.aulib.sql.api.SQLAction;
public interface SQLUpdateAction<T extends Number> extends SQLAction<T> {
/**
* 设定该操作返回自增键序列
*
* @return {@link SQLUpdateAction}
*/
SQLUpdateAction<T> returnGeneratedKey();
/**
* 设定该操作返回自增键序列
*
* @param keyTypeClass 自增序列的数字类型
* @param <N> 自增键序列类型 {@link Number}
* @return {@link SQLUpdateAction}
* @since 0.4.0
*/
<N extends Number> SQLUpdateAction<N> returnGeneratedKey(Class<N> keyTypeClass);
}

View File

@ -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<List<Integer>> {
/**
* 添加一条批量执行的SQL语句
*
* @param sql SQL语句
* @return {@link SQLUpdateBatchAction}
*/
SQLUpdateBatchAction addBatch(@NotNull String sql);
@Override
default @NotNull String getSQLContent() {
return getSQLContents().get(0);
}
@Override
@NotNull List<String> getSQLContents();
}

View File

@ -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<Object> params);
/**
* 直接对 {@link PreparedStatement} 进行处理
*
* @param statement {@link Consumer} 处理操作
* 若为空则不进行处理
* @return {@link PreparedQueryAction}
*/
PreparedQueryAction handleStatement(@Nullable Consumer<PreparedStatement> statement);
}

View File

@ -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查询语句并进行处理返回并自动关闭连接的基本类
*
* <ul>
* <li>同步执行 {@link #execute()}, {@link #execute(SQLFunction, SQLExceptionHandler)}
* <br>同步执行方法中有会抛出异常的方法与不抛出异常的方法
* <br>若选择不抛出异常则返回值可能为空需要特殊处理</li>
*
* <li>异步执行 {@link #executeAsync(SQLHandler, SQLExceptionHandler)}
* <br>异步执行时将提供成功与异常两种处理方式
* <br>可自行选择是否对数据或异常进行处理
* <br>默认的异常处理器为 {@link #defaultExceptionHandler()}</li>
* </ul>
*
* <b>注意 无论是否异步都不需要自行关闭ResultSet本API已自动关闭</b>
*
* @author CarmJos
* @since 0.2.6
*/
public interface QueryAction extends SQLAction<SQLQuery> {
@Override
@Contract("_,!null -> !null")
default <R> @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;
}
}
}

View File

@ -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<B extends ConditionalBuilder<B, T>, T extends SQLAction<?>> extends SQLBuilder {
/**
* 将现有条件构建完整的SQL语句用于执行
*
* @return {@link SQLAction}
*/
T build();
/**
* 设定限定的条目数
*
* @param limit 条数限制
* @return {@link B}
*/
B setLimit(int limit);
/**
* 直接设定条件的源文本,不需要以WHERE开头
* <br> {@code id = 1 AND name = 'test' OR name = 'test2'}
*
* @param condition 条件文本不需要以WHERE开头
* @return {@link B}
*/
B setConditions(@Nullable String condition);
/**
* 直接设定每个条件的文本与其对应数值,将以AND链接且不需要以WHERE开头
* <br>条件如 {@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);
}

View File

@ -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<DeleteBuilder, SQLAction<Integer>> {
String getTableName();
}

View File

@ -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<T extends SQLAction<?>> {
String getTableName();
T setColumnNames(List<String> columnNames);
default T setColumnNames(String... columnNames) {
return setColumnNames(columnNames == null ? null : Arrays.asList(columnNames));
}
}

View File

@ -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);
}

View File

@ -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 语句用于将一组值更新进数据表中
* <br> 执行后将通过表中键判断该数据是否存在若存在则用新数据替换原来的值若不存在则会插入该数据
* <br> 在使用REPLACE时表与所给行列数据中必须包含唯一索引(或主键)且索引不得为空值否则将等同于插入语句
*
* @param <T> 最终构建出的 {@link SQLAction} 类型
*/
public interface ReplaceBuilder<T extends SQLAction<?>> {
String getTableName();
T setColumnNames(List<String> columnNames);
default T setColumnNames(String... columnNames) {
return setColumnNames(columnNames == null ? null : Arrays.asList(columnNames));
}
}

View File

@ -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<Integer> renameTo(@NotNull String newTableName);
SQLAction<Integer> changeComment(@NotNull String newTableComment);
SQLAction<Integer> setAutoIncrementIndex(int index);
SQLAction<Integer> addIndex(@NotNull IndexType indexType, @Nullable String indexName,
@NotNull String columnName, @NotNull String... moreColumns);
/**
* 为该表移除一个索引
*
* @param indexName 索引名
* @return {@link SQLUpdateAction}
*/
SQLAction<Integer> dropIndex(@NotNull String indexName);
/**
* 为该表移除一个外键
*
* @param keySymbol 外键名
* @return {@link SQLUpdateAction}
*/
SQLAction<Integer> dropForeignKey(@NotNull String keySymbol);
/**
* 为该表移除主键(须添加新主键)
*
* @return {@link SQLUpdateAction}
*/
SQLAction<Integer> dropPrimaryKey();
/**
* 为表添加一列
*
* @param columnName 列名
* @param settings 列的相关设定
* @return {@link SQLUpdateAction}
*/
default SQLAction<Integer> addColumn(@NotNull String columnName, @NotNull String settings) {
return addColumn(columnName, settings, null);
}
/**
* 为表添加一列
*
* @param columnName 列名
* @param settings 列的相关设定
* @param afterColumn 该列增添到哪个列的后面
* <p> 该参数若省缺则放于最后一行
* <p> 若为 "" 则置于首行
* @return {@link SQLUpdateAction}
*/
SQLAction<Integer> addColumn(@NotNull String columnName, @NotNull String settings, @Nullable String afterColumn);
SQLAction<Integer> renameColumn(@NotNull String columnName, @NotNull String newName);
SQLAction<Integer> modifyColumn(@NotNull String columnName, @NotNull String settings);
default SQLAction<Integer> modifyColumn(@NotNull String columnName, @NotNull String columnSettings, @NotNull String afterColumn) {
return modifyColumn(columnName, columnSettings + " AFTER `" + afterColumn + "`");
}
SQLAction<Integer> removeColumn(@NotNull String columnName);
SQLAction<Integer> setColumnDefault(@NotNull String columnName, @NotNull String defaultValue);
SQLAction<Integer> removeColumnDefault(@NotNull String columnName);
/**
* 为该表添加一个自增列
* <p> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @param columnName 列名
* @param numberType 数字类型若省缺则为 {@link NumberType#INT}
* @param primary 是否为主键若否则只为唯一键
* @param unsigned 是否采用 UNSIGNED (即无负数可以增加自增键的最高数建议为true)
* @return {@link TableCreateBuilder}
*/
default SQLAction<Integer> 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"),
""
);
}
/**
* 为该表添加一个自增列
* <br> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @param columnName 列名
* @param numberType 数字类型若省缺则为 {@link NumberType#INT}
* @return {@link TableAlterBuilder}
*/
default SQLAction<Integer> addAutoIncrementColumn(@NotNull String columnName, @NotNull NumberType numberType) {
return addAutoIncrementColumn(columnName, numberType, false, true);
}
/**
* 为该表添加一个自增列
* <br> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @param columnName 列名
* @return {@link TableAlterBuilder}
*/
default SQLAction<Integer> addAutoIncrementColumn(@NotNull String columnName) {
return addAutoIncrementColumn(columnName, NumberType.INT);
}
}

View File

@ -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<Integer> build();
@NotNull String getTableName();
/**
* 得到表的设定
* <p> 若未使用 {@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 列的相关信息
* <br> `uuid` VARCHAR(36) NOT NULL UNIQUE KEY
* @return {@link TableCreateBuilder}
*/
TableCreateBuilder addColumn(@NotNull String column);
/**
* 为该表添加一个列
*
* @param columnName 列名
* @param settings 列的设定
* <br> 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 列的设定
* <br> 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));
}
/**
* 为该表添加一个自增列
* <p> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @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类型的自增主键列
* <p> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @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类型的自增列
* <p> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @param columnName 列名
* @param asPrimaryKey 是否为主键若为false则设定为唯一键
* @return {@link TableCreateBuilder}
*/
default TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName, boolean asPrimaryKey) {
return addAutoIncrementColumn(columnName, asPrimaryKey, true);
}
/**
* 为该表添加一个INT类型的自增主键列
* <p> 自增列强制要求为数字类型非空且为UNIQUE
* <p> 注意一个表只允许有一个自增列
*
* @param columnName 列名
* @return {@link TableCreateBuilder}
*/
default TableCreateBuilder addAutoIncrementColumn(@NotNull String columnName) {
return addAutoIncrementColumn(columnName, true);
}
/**
* 设定表中的某列为索引或键
*
* <p>创建索引时你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)
* <br>虽然索引大大提高了查询速度同时却会降低更新表的速度如对表进行INSERTUPDATE 和DELETE
* <br>因此请合理的设计索引
*
* @param type 索引类型
* @param columnName 索引包含的列
* @return {@link TableCreateBuilder}
*/
default TableCreateBuilder setIndex(@NotNull String columnName,
@NotNull IndexType type) {
return setIndex(type, null, columnName);
}
/**
* 设定表中的某列为索引或键
*
* <p>创建索引时你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)
* <br>虽然索引大大提高了查询速度同时却会降低更新表的速度如对表进行INSERTUPDATE 和DELETE
* <br>因此请合理的设计索引
*
* @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);
/**
* 以本表位从表为表中某列设定自参照外键(即自参照完整性)
*
* <p>外键约束FOREIGN KEY是表的一个特殊字段经常与主键约束一起使用
* <br>外键用来建立主表与从表的关联关系为两个表的数据建立连接约束两个表中数据的一致性和完整性
* <br>主表删除某条记录时从表中与之对应的记录也必须有相应的改变
*
* @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);
}
/**
* 以本表位从表为表中某列设定外键
*
* <p>外键约束FOREIGN KEY是表的一个特殊字段经常与主键约束一起使用
* <br>外键用来建立主表与从表的关联关系为两个表的数据建立连接约束两个表中数据的一致性和完整性
* <br>主表删除某条记录时从表中与之对应的记录也必须有相应的改变
*
* @param tableColumn 本表中的列
* @param foreignTable 外键关联主表必须为已存在的表或本表且必须有主键
* @param foreignColumn 外键关联主表中对应的关联列须满足
* <p> 1. 为主表的主键 {@link IndexType#PRIMARY_KEY}
* <p> 2. 数据类型必须和所要建立主键的列的数据类型相同
* @return {@link TableCreateBuilder}
*/
default TableCreateBuilder addForeignKey(@NotNull String tableColumn,
@NotNull String foreignTable, @NotNull String foreignColumn) {
return addForeignKey(tableColumn, null, foreignTable, foreignColumn);
}
/**
* 以本表位从表为表中某列设定外键
*
* <p>外键约束FOREIGN KEY是表的一个特殊字段经常与主键约束一起使用
* <br>外键用来建立主表与从表的关联关系为两个表的数据建立连接约束两个表中数据的一致性和完整性
* <br>主表删除某条记录时从表中与之对应的记录也必须有相应的改变
*
* @param tableColumn 本表中的列
* @param constraintName 约束名缺省时将使用参数自动生成 <i>fk_[tableColumn]_[foreignTable]</i>
* @param foreignTable 外键关联主表必须为已存在的表或本表且必须有主键
* @param foreignColumn 外键关联主表中对应的关联列须满足
* <p> 1. 为主表的主键 {@link IndexType#PRIMARY_KEY}
* <p> 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);
}
/**
* 以本表位从表为表中某列设定外键
*
* <p>外键约束FOREIGN KEY是表的一个特殊字段经常与主键约束一起使用
* <br>外键用来建立主表与从表的关联关系为两个表的数据建立连接约束两个表中数据的一致性和完整性
* <br>主表删除某条记录时从表中与之对应的记录也必须有相应的改变
*
* @param tableColumn 本表中的列
* @param constraintName 约束名缺省时将使用参数自动生成 <i>fk_[tableColumn]_[foreignTable]</i>
* @param foreignTable 外键关联主表必须为已存在的表或本表且必须有主键
* @param foreignColumn 外键关联主表中对应的关联列须满足
* <p> 1. 为主表的主键 {@link IndexType#PRIMARY_KEY}
* <p> 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";
}
}

View File

@ -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<Boolean> validateExist();
/**
* 对表内的数据列元数据进行读取
*
* @param columnPattern 列的名称匹配表达式, 为空则匹配所有列
* @param reader 读取的方法
* @param <R> 结果类型
* @return 读取结果
*/
<R> CompletableFuture<R> fetchColumns(@Nullable String columnPattern,
@NotNull SQLFunction<ResultSet, R> reader);
/**
* @param columnPattern 需要判断的列名表达式
* @return 对应列是否存在
*/
CompletableFuture<Boolean> isColumnExists(@NotNull String columnPattern);
/**
* 列出所有表内的全部列
*
* @return 表内全部数据列的列名
*/
default CompletableFuture<@Unmodifiable Set<String>> listColumns() {
return listColumns(null);
}
/**
* 列出所有满足表达式的列
*
* @param columnPattern 列名表达式为空则列出全部
* @return 所有满足表达式的列名
*/
CompletableFuture<@Unmodifiable Set<String>> listColumns(@Nullable String columnPattern);
// More coming soon.
}

View File

@ -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<TableQueryBuilder, PreparedQueryAction> {
@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);
}

View File

@ -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<UpdateBuilder, SQLAction<Integer>> {
String getTableName();
/**
* 添加一条需要更新的字段名与值
*
* @param columnName 字段名
* @param columnValue 字段名对应的值
* @return {@link UpdateBuilder}
* @since 0.3.7
*/
UpdateBuilder addColumnValue(@NotNull String columnName, @Nullable Object columnValue);
/**
* 设定更新的全部字段值 <b>(此操作会覆盖之前的设定)</b>
* <p> <b>此操作会覆盖之前的设定</b>
*
* @param columnData 字段名和值的键值对
* @return {@link UpdateBuilder}
*/
UpdateBuilder setColumnValues(LinkedHashMap<@NotNull String, @Nullable Object> columnData);
/**
* 设定更新的全部字段值 <b>(此操作会覆盖之前的设定)</b>
* <p> <b>此操作会覆盖之前的设定</b>
*
* @param columnNames 字段名
* @param columnValues 字段名对应的值
* @return {@link UpdateBuilder}
*/
UpdateBuilder setColumnValues(@NotNull String[] columnNames, @Nullable Object[] columnValues);
/**
* 设定更新的全部字段值 <b>(此操作会覆盖之前的设定)</b>
* <p> 如需同时更新多条字段请使用 {@link #setColumnValues(String[], Object[])} {@link #setColumnValues(LinkedHashMap)}
* <br>也可以使用 {@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});
}
}

View File

@ -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 .");
}
}

View File

@ -0,0 +1,41 @@
package com.io.yutian.aulib.sql.api.enums;
public enum ForeignKeyRule {
/**
* 啥也不做
* <p>注意 在Mysql中该选项实际上等同于采用默认的 {@link #RESTRICT} 设定
*/
NO_ACTION("NO ACTION"),
/**
* 拒绝删除要求直到使用删除键值的辅助表被手工删除并且没有参照时(这是默认设置也是最安全的设置)
*/
RESTRICT("RESTRICT"),
/**
* 修改包含与已删除键值有参照关系的所有记录使用NULL值替换只能用于已标记为NOT NULL的字段
*/
SET_NULL("SET NULL"),
/**
* 修改包含与已删除键值有参照关系的所有记录使用默认值替换只能用于设定了DEFAULT的字段
*/
SET_DEFAULT("SET DEFAULT"),
/**
* <b>级联删除</b>删除包含与已删除键值有参照关系的所有记录
*/
CASCADE("CASCADE");
final String ruleName;
ForeignKeyRule(String ruleName) {
this.ruleName = ruleName;
}
public String getRuleName() {
return ruleName;
}
}

View File

@ -0,0 +1,41 @@
package com.io.yutian.aulib.sql.api.enums;
public enum IndexType {
/**
* <b>普通索引</b>由关键字KEY或INDEX定义的索引的唯一任务是加快对数据的访问速度
* <br> 因此应该只为那些最经常出现在查询条件WHERE column=或排序条件ORDER BY column中的数据列创建索引
* <br> 只要有可能就应该选择一个数据最整齐最紧凑的数据列如一个整数类型的数据列来创建索引
*/
INDEX("INDEX"),
/**
* <b>唯一索引</b> 是在表上一个或者多个字段组合建立的索引这个或者这些字段的值组合起来在表中不可以重复用于保证数据的唯一性
*/
UNIQUE_KEY("UNIQUE KEY"),
/**
* <b>主键索引</b> 是唯一索引的特定类型表中创建主键时自动创建的索引 一个表只能建立一个主索引
*/
PRIMARY_KEY("PRIMARY KEY"),
/**
* <b>全文索引</b> 主要用来查找文本中的关键字而不是直接与索引中的值相比较
* <br> 请搭配 MATCH 等语句使用而不是使用 WHERE - LIKE
* <br> 全文索引只可用于 CHAR VARCHAR TEXT 系列类型
*/
FULLTEXT_INDEX("FULLTEXT");
final String name;
IndexType(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,11 @@
package com.io.yutian.aulib.sql.api.enums;
public enum NumberType {
TINYINT,
SMALLINT,
MEDIUMINT,
INT,
BIGINT
}

View File

@ -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<T, U, R> {
@Nullable
R apply(@NotNull T t, @NotNull U u) throws SQLException;
default <V> SQLBiFunction<T, U, V> then(@NotNull SQLFunction<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> {
R r = apply(t, u);
if (r == null) return null;
else return after.apply(r);
};
}
}

View File

@ -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;
/**
* 异常处理器
* <br> 在使用 {@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<String> 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("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}
};
}
}

View File

@ -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;
/**
* 异常处理器
* <br> 在使用 {@link SQLAction#execute(SQLExceptionHandler)} 等相关方法时
* 如果发生异常则会调用错误处理器进行错误内容的输出提示
*/
@FunctionalInterface
public interface SQLExceptionHandler extends BiConsumer<SQLException, SQLAction<?>> {
/**
* 默认的异常处理器将详细的输出相关错误与错误来源
*
* @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) -> {
};
}
}

View File

@ -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<T, R> {
@Nullable
R apply(@NotNull T t) throws SQLException;
default <V> SQLFunction<V, R> compose(@NotNull SQLFunction<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> {
T t = before.apply(v);
if (t == null) return null;
else return apply(t);
};
}
default <V> SQLFunction<T, V> then(@NotNull SQLFunction<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> {
R r = apply(t);
if (r == null) return null;
else return after.apply(r);
};
}
}

View File

@ -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<T> {
void accept(@NotNull T t) throws SQLException;
@NotNull
@Contract(pure = true)
default SQLHandler<T> andThen(@NotNull SQLHandler<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
accept(t);
after.accept(t);
};
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -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<B extends ConditionalBuilder<B, T>, T extends SQLAction<?>>
extends AbstractSQLBuilder implements ConditionalBuilder<B, T> {
ArrayList<String> conditionSQLs = new ArrayList<>();
ArrayList<Object> 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<String> 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<Object> getConditionParams() {
return conditionParams;
}
protected boolean hasConditions() {
return this.conditionSQLs != null && !this.conditionSQLs.isEmpty();
}
protected boolean hasConditionParams() {
return hasConditions() && getConditionParams() != null && !getConditionParams().isEmpty();
}
}

View File

@ -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<DeleteBuilder, SQLAction<Integer>>
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<Integer> 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;
}
}

View File

@ -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<T extends SQLAction<?>>
extends AbstractSQLBuilder implements InsertBuilder<T> {
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<String> columnNames) {
return buildSQL("INSERT IGNORE INTO", tableName, columnNames);
}
protected static String buildSQL(String sqlPrefix, String tableName, List<String> columnNames) {
int valueLength = columnNames.size();
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append(sqlPrefix).append(" ").append(withBackQuote(tableName)).append("(");
Iterator<String> 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;
}
}

View File

@ -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);
}
}

View File

@ -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<T extends SQLAction<?>>
extends AbstractSQLBuilder implements ReplaceBuilder<T> {
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<String> columnNames) {
return InsertBuilderImpl.buildSQL("REPLACE INTO", tableName, columnNames);
}
@Override
public @NotNull String getTableName() {
return tableName;
}
}

View File

@ -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<Integer> 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<Integer> 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<Integer> setAutoIncrementIndex(int index) {
return createAction("ALTER TABLE " + withBackQuote(getTableName()) + " AUTO_INCREMENT=" + index);
}
@Override
public SQLAction<Integer> 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<Integer> 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<Integer> 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<Integer> dropPrimaryKey() {
return createAction(
"ALTER TABLE " + withBackQuote(getTableName()) + " DROP PRIMARY KEY"
);
}
@Override
public SQLAction<Integer> 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<Integer> 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<Integer> 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<Integer> removeColumn(@NotNull String columnName) {
Objects.requireNonNull(columnName, "columnName could not be null");
return createAction(
"ALTER TABLE " + withBackQuote(getTableName()) + " DROP " + withBackQuote(columnName)
);
}
@Override
public SQLAction<Integer> 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<Integer> 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<Integer> createAction(@NotNull String sql) {
return new SQLUpdateActionImpl<>(getManager(), Integer.class, sql);
}
}

View File

@ -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<String> indexes = new ArrayList<>();
@NotNull
final List<String> foreignKeys = new ArrayList<>();
@NotNull List<String> 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<Integer> 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;
}
}

View File

@ -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<Boolean> validateExist() {
return validate((meta, conn) -> meta.getTables(conn.getCatalog(), conn.getSchema(), tablePattern.toUpperCase(), new String[]{"TABLE"}));
}
@Override
public <R> CompletableFuture<R> fetchColumns(@Nullable String columnPattern,
@NotNull SQLFunction<ResultSet, R> 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<Boolean> isColumnExists(@NotNull String columnPattern) {
return validate((meta, conn) -> meta.getColumns(
conn.getCatalog(), conn.getSchema(),
tablePattern.toUpperCase(), columnPattern.toUpperCase()
));
}
@Override
public CompletableFuture<Set<String>> listColumns(@Nullable String columnPattern) {
return fetchColumns(columnPattern, (rs) -> {
Set<String> 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<Boolean> validate(SQLBiFunction<DatabaseMetaData, Connection, ResultSet> supplier) {
return getManager().fetchMetadata(supplier, ResultSet::next);
}
}

View File

@ -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<TableQueryBuilder, PreparedQueryAction>
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;
}
}

View File

@ -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<UpdateBuilder, SQLAction<Integer>>
implements UpdateBuilder {
protected final @NotNull String tableName;
@NotNull LinkedHashMap<String, Object> columnData;
public UpdateBuilderImpl(@NotNull SQLManagerImpl manager, @NotNull String tableName) {
super(manager);
this.tableName = tableName;
this.columnData = new LinkedHashMap<>();
}
@Override
public PreparedSQLUpdateAction<Integer> build() {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("UPDATE ").append(withBackQuote(getTableName())).append(" SET ");
Iterator<String> iterator = this.columnData.keySet().iterator();
while (iterator.hasNext()) {
sqlBuilder.append(withBackQuote(iterator.next())).append(" = ?");
if (iterator.hasNext()) sqlBuilder.append(" , ");
}
List<Object> 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<String, Object> 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<String, Object> 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;
}
}

View File

@ -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<UUID, SQLQuery> activeQuery = new ConcurrentHashMap<>();
protected ExecutorService executorPool;
@NotNull Supplier<Boolean> 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<UUID, SQLQuery> 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<Integer> executeSQLBatch(String sql, Iterable<Object[]> paramsBatch) {
return new PreparedSQLBatchUpdateActionImpl<>(this, Integer.class, sql).setAllParams(paramsBatch).execute(null);
}
@Override
public List<Integer> 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<Integer> executeSQLBatch(@NotNull Iterable<String> sqlBatch) {
Iterator<String> 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 <R> CompletableFuture<R> fetchMetadata(@NotNull SQLBiFunction<DatabaseMetaData, Connection, R> 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 <R> CompletableFuture<R> fetchMetadata(@NotNull SQLBiFunction<DatabaseMetaData, Connection, ResultSet> 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<PreparedSQLUpdateBatchAction<Integer>> createInsertBatch(@NotNull String tableName) {
return new InsertBuilderImpl<PreparedSQLUpdateBatchAction<Integer>>(this, tableName) {
@Override
public PreparedSQLUpdateBatchAction<Integer> setColumnNames(List<String> columnNames) {
return new PreparedSQLBatchUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames));
}
};
}
@Override
public InsertBuilder<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull String tableName) {
return new InsertBuilderImpl<PreparedSQLUpdateAction<Integer>>(this, tableName) {
@Override
public PreparedSQLUpdateAction<Integer> setColumnNames(List<String> columnNames) {
return new PreparedSQLUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames));
}
};
}
@Override
public ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> createReplaceBatch(@NotNull String tableName) {
return new ReplaceBuilderImpl<PreparedSQLUpdateBatchAction<Integer>>(this, tableName) {
@Override
public PreparedSQLUpdateBatchAction<Integer> setColumnNames(List<String> columnNames) {
return new PreparedSQLBatchUpdateActionImpl<>(getManager(), Integer.class, buildSQL(getTableName(), columnNames));
}
};
}
@Override
public ReplaceBuilder<PreparedSQLUpdateAction<Integer>> createReplace(@NotNull String tableName) {
return new ReplaceBuilderImpl<PreparedSQLUpdateAction<Integer>>(this, tableName) {
@Override
public PreparedSQLUpdateAction<Integer> setColumnNames(List<String> 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);
}
}

View File

@ -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<PreparedStatement> 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<Object> params) {
if (params == null) {
return setParams((Object[]) null);
} else {
List<Object> paramsList = new ArrayList<>();
params.forEach(paramsList::add);
return setParams(paramsList.toArray());
}
}
@Override
public PreparedQueryActionImpl handleStatement(@Nullable Consumer<PreparedStatement> 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;
}
}
}

View File

@ -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<SQLQuery> 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<SQLQuery> success, SQLExceptionHandler failure) {
getManager().getExecutorPool().submit(() -> {
try (SQLQueryImpl query = execute()) {
if (success != null) success.accept(query);
} catch (SQLException exception) {
handleException(failure, exception);
}
});
}
}

View File

@ -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;
}
}

View File

@ -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<Integer, Integer> 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<Object[]> 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<Object[]> paramsBatch, boolean returnGeneratedKey
) throws SQLException {
sql = sql.trim();
PreparedStatement statement = connection.prepareStatement(sql, returnGeneratedKey ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS);
Map<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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);
}
}