mirror of
https://github.com/CarmJos/EasySQL.git
synced 2026-06-04 15:28:20 +08:00
chore(sql): 提供当前事务实现思路
This commit is contained in:
@@ -1,26 +1,52 @@
|
||||
package cc.carm.lib.easysql.api;
|
||||
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateAction;
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateBatchAction;
|
||||
import cc.carm.lib.easysql.api.transaction.SQLTransaction;
|
||||
import cc.carm.lib.easysql.api.builder.*;
|
||||
import cc.carm.lib.easysql.api.builder.TableAlterBuilder;
|
||||
import cc.carm.lib.easysql.api.builder.TableCreateBuilder;
|
||||
import cc.carm.lib.easysql.api.builder.TableMetadataBuilder;
|
||||
import cc.carm.lib.easysql.api.enums.IsolationLevel;
|
||||
import cc.carm.lib.easysql.api.function.SQLBiFunction;
|
||||
import cc.carm.lib.easysql.api.function.SQLFunction;
|
||||
import cc.carm.lib.easysql.api.transaction.SQLTransaction;
|
||||
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.concurrent.CompletableFuture;
|
||||
|
||||
public interface NewSQLManager {
|
||||
public interface NewSQLManager extends SQLSource, SQLOperator {
|
||||
|
||||
default @NotNull SQLTransaction createTransaction() {
|
||||
return createTransaction(null);
|
||||
}
|
||||
|
||||
@NotNull SQLTransaction createTransaction(@Nullable IsolationLevel level);
|
||||
|
||||
/**
|
||||
* 得到用于该管理器的 {@link SQLSource}
|
||||
* 在库中创建一个表。
|
||||
*
|
||||
* @return {@link SQLSource}
|
||||
* @param tableName 表名
|
||||
* @return {@link TableCreateBuilder}
|
||||
*/
|
||||
@NotNull SQLSource getSource();
|
||||
@NotNull TableCreateBuilder createTable(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 对库中的某个表执行更改。
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return {@link TableAlterBuilder}
|
||||
*/
|
||||
@NotNull TableAlterBuilder alterTable(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 快速获取表的部分元数据。
|
||||
* <br> 当需要获取其他元数据时,请使用 {@link #fetchMetadata(SQLFunction, SQLFunction)} 方法。
|
||||
*
|
||||
* @param tablePattern 表名通配符
|
||||
* @return {@link TableMetadataBuilder}
|
||||
*/
|
||||
@NotNull TableMetadataBuilder fetchTableMetadata(@NotNull String tablePattern);
|
||||
|
||||
/**
|
||||
* 获取并操作 {@link DatabaseMetaData} 以得到需要的数据库消息。
|
||||
@@ -70,86 +96,4 @@ public interface NewSQLManager {
|
||||
<R> CompletableFuture<R> fetchMetadata(@NotNull SQLBiFunction<DatabaseMetaData, Connection, ResultSet> supplier,
|
||||
@NotNull SQLFunction<@NotNull ResultSet, R> reader);
|
||||
|
||||
/**
|
||||
* 在库中创建一个表。
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return {@link TableCreateBuilder}
|
||||
*/
|
||||
@NotNull TableCreateBuilder createTable(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 对库中的某个表执行更改。
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return {@link TableAlterBuilder}
|
||||
*/
|
||||
@NotNull TableAlterBuilder alterTable(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 快速获取表的部分元数据。
|
||||
* <br> 当需要获取其他元数据时,请使用 {@link #fetchMetadata(SQLFunction, SQLFunction)} 方法。
|
||||
*
|
||||
* @param tablePattern 表名通配符
|
||||
* @return {@link TableMetadataBuilder}
|
||||
*/
|
||||
@NotNull TableMetadataBuilder fetchTableMetadata(@NotNull String tablePattern);
|
||||
|
||||
/**
|
||||
* 新建一个查询。
|
||||
*
|
||||
* @return {@link QueryBuilder}
|
||||
*/
|
||||
@NotNull QueryBuilder createQuery();
|
||||
|
||||
@NotNull SQLTransaction createTransaction();
|
||||
|
||||
/**
|
||||
* 创建一条插入操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link InsertBuilder}
|
||||
*/
|
||||
@NotNull InsertBuilder<PreparedSQLUpdateAction<Integer>> insertInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建支持多组数据的插入操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link InsertBuilder}
|
||||
*/
|
||||
@NotNull InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> insertBatchInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建一条替换操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link ReplaceBuilder}
|
||||
*/
|
||||
@NotNull ReplaceBuilder<PreparedSQLUpdateAction<Integer>> replaceInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建支持多组数据的替换操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link ReplaceBuilder}
|
||||
*/
|
||||
@NotNull ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> replaceBatchInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建更新操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link UpdateBuilder}
|
||||
*/
|
||||
@NotNull UpdateBuilder updateInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建删除操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link DeleteBuilder}
|
||||
*/
|
||||
@NotNull DeleteBuilder deleteFrom(@NotNull String tableName);
|
||||
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ public interface SQLManager {
|
||||
* @param tableName 目标表名
|
||||
* @return {@link InsertBuilder}
|
||||
*/
|
||||
InsertBuilder<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull String tableName);
|
||||
InsertBuilder<PreparedSQLUpdateAction<Integer>> insertInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建支持多组数据的插入操作。
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cc.carm.lib.easysql.api;
|
||||
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateAction;
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateBatchAction;
|
||||
import cc.carm.lib.easysql.api.builder.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface SQLOperator {
|
||||
|
||||
/**
|
||||
* 新建一个查询。
|
||||
*
|
||||
* @return {@link QueryBuilder}
|
||||
*/
|
||||
@NotNull QueryBuilder createQuery();
|
||||
|
||||
/**
|
||||
* 创建一条插入操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link InsertBuilder}
|
||||
*/
|
||||
@NotNull InsertBuilder<PreparedSQLUpdateAction<Integer>> insertInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建支持多组数据的插入操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link InsertBuilder}
|
||||
*/
|
||||
@NotNull InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> insertBatchInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建一条替换操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link ReplaceBuilder}
|
||||
*/
|
||||
@NotNull ReplaceBuilder<PreparedSQLUpdateAction<Integer>> replaceInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建支持多组数据的替换操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link ReplaceBuilder}
|
||||
*/
|
||||
@NotNull ReplaceBuilder<PreparedSQLUpdateBatchAction<Integer>> replaceBatchInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建更新操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link UpdateBuilder}
|
||||
*/
|
||||
@NotNull UpdateBuilder updateInto(@NotNull String tableName);
|
||||
|
||||
/**
|
||||
* 创建删除操作。
|
||||
*
|
||||
* @param tableName 目标表名
|
||||
* @return {@link DeleteBuilder}
|
||||
*/
|
||||
@NotNull DeleteBuilder deleteFrom(@NotNull String tableName);
|
||||
|
||||
}
|
||||
@@ -9,18 +9,18 @@ public interface PreparedSQLUpdateBatchAction<T extends Number> extends SQLActio
|
||||
/**
|
||||
* 设定多组SQL语句中所有 ? 对应的参数
|
||||
*
|
||||
* @param allParams 所有参数内容
|
||||
* @param allValues 所有参数内容
|
||||
* @return {@link PreparedSQLUpdateBatchAction}
|
||||
*/
|
||||
PreparedSQLUpdateBatchAction<T> setAllParams(Iterable<Object[]> allParams);
|
||||
PreparedSQLUpdateBatchAction<T> allValues(Iterable<Object[]> allValues);
|
||||
|
||||
/**
|
||||
* 添加一组SQL语句中所有 ? 对应的参数
|
||||
*
|
||||
* @param params 参数内容
|
||||
* @param values 参数内容
|
||||
* @return {@link PreparedSQLUpdateBatchAction}
|
||||
*/
|
||||
PreparedSQLUpdateBatchAction<T> addParamsBatch(Object... params);
|
||||
PreparedSQLUpdateBatchAction<T> values(Object... values);
|
||||
|
||||
/**
|
||||
* 设定该操作返回自增键序列。
|
||||
|
||||
@@ -49,7 +49,7 @@ public interface TableCreateBuilder extends SQLBuilder {
|
||||
* @param columns 列的相关信息 (包括列设定)
|
||||
* @return {@link TableCreateBuilder}
|
||||
*/
|
||||
TableCreateBuilder columns(@NotNull String... columns);
|
||||
TableCreateBuilder setColumns(@NotNull String... columns);
|
||||
|
||||
/**
|
||||
* 为该表添加一个列
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package cc.carm.lib.easysql.api.enums;
|
||||
|
||||
/**
|
||||
* 事务隔离级别。
|
||||
* 部分Javadoc来自文章 <a href="https://cloud.tencent.com/developer/article/1865041">《理解事务的4种隔离级别》</a> 。
|
||||
*/
|
||||
public enum IsolationLevel {
|
||||
|
||||
/**
|
||||
* 读未提交。即一个事务可以读取另一个未提交事务的数据。
|
||||
*/
|
||||
READ_UNCOMMITTED,
|
||||
/**
|
||||
* 读提交。即一个事务要等另一个事务提交后才能读取数据。
|
||||
*/
|
||||
READ_COMMITTED,
|
||||
/**
|
||||
* 重复读。即在开始读取数据(事务开启)时,不再允许修改操作。
|
||||
*/
|
||||
REPEATED_READ,
|
||||
/**
|
||||
* 序列化读取。此为最高隔离等级。在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。
|
||||
* <br>注意: 这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
|
||||
*/
|
||||
SERIALIZABLE;
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cc.carm.lib.easysql.api.table;
|
||||
|
||||
import cc.carm.lib.easysql.api.SQLManager;
|
||||
import cc.carm.lib.easysql.api.SQLTable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -16,7 +15,7 @@ public abstract class NamedSQLTable implements SQLTable {
|
||||
protected @Nullable SQLManager manager;
|
||||
|
||||
/**
|
||||
* 请调用 {@link NamedSQLTable} 下的静态方法进行对象的初始化。
|
||||
* 请调用 {@link SQLTable} 下的静态方法进行对象的初始化。
|
||||
*
|
||||
* @param tableName 该表的名称
|
||||
*/
|
||||
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
package cc.carm.lib.easysql.api;
|
||||
package cc.carm.lib.easysql.api.table;
|
||||
|
||||
import cc.carm.lib.easysql.api.SQLManager;
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateAction;
|
||||
import cc.carm.lib.easysql.api.action.PreparedSQLUpdateBatchAction;
|
||||
import cc.carm.lib.easysql.api.builder.*;
|
||||
@@ -45,7 +46,7 @@ public interface SQLTable {
|
||||
static @NotNull NamedSQLTable of(@NotNull String tableName,
|
||||
@NotNull String[] columns, @Nullable String tableSettings) {
|
||||
return of(tableName, builder -> {
|
||||
builder.columns(columns);
|
||||
builder.setColumns(columns);
|
||||
if (tableSettings != null) builder.setTableSettings(tableSettings);
|
||||
});
|
||||
}
|
||||
@@ -106,7 +107,7 @@ public interface SQLTable {
|
||||
}
|
||||
|
||||
default @NotNull InsertBuilder<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull SQLManager sqlManager) {
|
||||
return sqlManager.createInsert(getTableName());
|
||||
return sqlManager.insertInto(getTableName());
|
||||
}
|
||||
|
||||
default @NotNull InsertBuilder<PreparedSQLUpdateBatchAction<Integer>> createInsertBatch() {
|
||||
@@ -12,5 +12,4 @@ public interface SQLSavepoint {
|
||||
|
||||
boolean isReleased();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,44 @@
|
||||
package cc.carm.lib.easysql.api.transaction;
|
||||
|
||||
import cc.carm.lib.easysql.api.NewSQLManager;
|
||||
import cc.carm.lib.easysql.api.SQLOperator;
|
||||
import cc.carm.lib.easysql.api.enums.IsolationLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface SQLTransaction extends NewSQLManager, AutoCloseable {
|
||||
public interface SQLTransaction extends SQLOperator, AutoCloseable {
|
||||
|
||||
/**
|
||||
* 得到本次事务的隔离级别
|
||||
*
|
||||
* @return {@link IsolationLevel} 隔离级别
|
||||
*/
|
||||
@NotNull IsolationLevel getIsolationLevel();
|
||||
|
||||
/**
|
||||
* 提交已有操作
|
||||
*/
|
||||
void commit();
|
||||
|
||||
SQLSavepoint savepoint(String name);
|
||||
/**
|
||||
* 设定一个记录点
|
||||
*
|
||||
* @param name 记录点名称
|
||||
* @return {@link SQLSavepoint} 事务记录点
|
||||
*/
|
||||
@NotNull SQLSavepoint savepoint(@NotNull String name);
|
||||
|
||||
/**
|
||||
* 回退全部操作
|
||||
*/
|
||||
default void rollback() {
|
||||
rollback(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退操作到某个记录点或回退整个事务操作。
|
||||
*
|
||||
* @param savepoint 记录点,若记录点为NULL则回退整个事务的操作。
|
||||
*/
|
||||
void rollback(@Nullable SQLSavepoint savepoint);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cc.carm.lib.easysql.api.SQLManager;
|
||||
import cc.carm.lib.easysql.api.SQLTable;
|
||||
import cc.carm.lib.easysql.api.table.SQLTable;
|
||||
import cc.carm.lib.easysql.api.enums.IndexType;
|
||||
import cc.carm.lib.easysql.api.enums.NumberType;
|
||||
import cc.carm.lib.easysql.api.table.NamedSQLTable;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cc.carm.lib.easysql.api.SQLManager;
|
||||
import cc.carm.lib.easysql.api.SQLTable;
|
||||
import cc.carm.lib.easysql.api.table.SQLTable;
|
||||
import cc.carm.lib.easysql.api.builder.TableCreateBuilder;
|
||||
import cc.carm.lib.easysql.api.enums.IndexType;
|
||||
import cc.carm.lib.easysql.api.enums.NumberType;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import cc.carm.lib.easysql.api.SQLManager;
|
||||
import cc.carm.lib.easysql.api.SQLQuery;
|
||||
import cc.carm.lib.easysql.api.SQLTable;
|
||||
import cc.carm.lib.easysql.api.table.SQLTable;
|
||||
import cc.carm.lib.easysql.api.enums.ForeignKeyRule;
|
||||
import cc.carm.lib.easysql.api.enums.IndexType;
|
||||
import cc.carm.lib.easysql.api.enums.NumberType;
|
||||
@@ -136,7 +136,7 @@ public class EasySQLDemo {
|
||||
|
||||
public void sqlInsert(SQLManager sqlManager) {
|
||||
// 同步SQL插入 (不使用try-catch的情况下,返回的数值可能为空。)
|
||||
int id = sqlManager.createInsert("users")
|
||||
int id = sqlManager.insertInto("users")
|
||||
.columns("username", "phone", "email", "registerTime")
|
||||
.values("CarmJos", "18888888888", "carm@carm.cc", TimeDateUtils.getCurrentTime())
|
||||
.returnGeneratedKey() // 设定在后续返回自增主键
|
||||
@@ -147,10 +147,10 @@ public class EasySQLDemo {
|
||||
});
|
||||
|
||||
try {
|
||||
int userID = sqlManager.createInsert("users")
|
||||
long userID = sqlManager.insertInto("users")
|
||||
.columns("username", "phone", "email", "registerTime")
|
||||
.values("CarmJos", "18888888888", "carm@carm.cc", TimeDateUtils.getCurrentTime())
|
||||
.returnGeneratedKey().execute();
|
||||
.returnGeneratedKey(Long.class).execute();
|
||||
|
||||
System.out.println("新用户的ID为 " + userID);
|
||||
|
||||
|
||||
@@ -27,10 +27,9 @@ public class TransactionTest {
|
||||
transaction.commit(); // 提交
|
||||
} catch (Exception ex) {
|
||||
transaction.rollback(pointA); // 出错回滚到pointA
|
||||
transaction.commit(); // 提交出错前的内容
|
||||
transaction.commit(); // 提交快照前的代码
|
||||
}
|
||||
|
||||
|
||||
pointA.release(); // release savepoint (结束后也会被自动释放)
|
||||
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -27,7 +27,7 @@ public class SQLUpdateBatchTests extends TestHandler {
|
||||
|
||||
List<Long> updates = sqlManager.createInsertBatch("test_user_table")
|
||||
.columns("uuid", "username", "age")
|
||||
.setAllParams(generateParams())
|
||||
.allValues(generateParams())
|
||||
.returnGeneratedKeys(Long.class)
|
||||
.execute();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SQLUpdateReturnKeysTest extends SQLUpdateBatchTests {
|
||||
public void onTest(SQLManager sqlManager) throws SQLException {
|
||||
List<Integer> generatedKeys = sqlManager.createInsertBatch("test_user_table")
|
||||
.columns("uuid", "username", "age")
|
||||
.setAllParams(generateParams())
|
||||
.allValues(generateParams())
|
||||
.returnGeneratedKeys(Integer.class)
|
||||
.execute();
|
||||
|
||||
|
||||
@@ -38,16 +38,16 @@ public class PreparedSQLBatchUpdateActionImpl<T extends Number>
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreparedSQLBatchUpdateActionImpl<T> setAllParams(Iterable<Object[]> allParams) {
|
||||
public PreparedSQLBatchUpdateActionImpl<T> allValues(Iterable<Object[]> allValues) {
|
||||
List<Object[]> paramsList = new ArrayList<>();
|
||||
allParams.forEach(paramsList::add);
|
||||
allValues.forEach(paramsList::add);
|
||||
this.allParams = paramsList;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreparedSQLBatchUpdateActionImpl<T> addParamsBatch(Object... params) {
|
||||
this.allParams.add(params);
|
||||
public PreparedSQLBatchUpdateActionImpl<T> values(Object... values) {
|
||||
this.allParams.add(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class PreparedSQLBatchUpdateActionImpl<T extends Number>
|
||||
@Override
|
||||
public <N extends Number> PreparedSQLBatchUpdateActionImpl<N> returnGeneratedKeys(Class<N> keyTypeClass) {
|
||||
return new PreparedSQLBatchUpdateActionImpl<>(getManager(), keyTypeClass, getActionUUID(), getSQLContent())
|
||||
.setAllParams(allParams).returnGeneratedKeys();
|
||||
.allValues(allParams).returnGeneratedKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -150,7 +150,7 @@ public class TableCreateBuilderImpl extends AbstractSQLBuilder implements TableC
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableCreateBuilder columns(@NotNull String... columns) {
|
||||
public TableCreateBuilder setColumns(@NotNull String... columns) {
|
||||
Objects.requireNonNull(columns, "columns could not be null");
|
||||
this.columns = Arrays.asList(columns);
|
||||
return this;
|
||||
|
||||
@@ -130,7 +130,7 @@ public class SQLManagerImpl implements SQLManager {
|
||||
|
||||
@Override
|
||||
public List<Integer> executeSQLBatch(String sql, Iterable<Object[]> paramsBatch) {
|
||||
return new PreparedSQLBatchUpdateActionImpl<>(this, Integer.class, sql).setAllParams(paramsBatch).execute(null);
|
||||
return new PreparedSQLBatchUpdateActionImpl<>(this, Integer.class, sql).allValues(paramsBatch).execute(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,7 +210,7 @@ public class SQLManagerImpl implements SQLManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertBuilder<PreparedSQLUpdateAction<Integer>> createInsert(@NotNull String tableName) {
|
||||
public InsertBuilder<PreparedSQLUpdateAction<Integer>> insertInto(@NotNull String tableName) {
|
||||
return new InsertBuilderImpl<PreparedSQLUpdateAction<Integer>>(this, tableName) {
|
||||
@Override
|
||||
public PreparedSQLUpdateAction<Integer> columns(List<String> columnNames) {
|
||||
|
||||
Reference in New Issue
Block a user