前情回顾
【设计】设计一个web版的数据库管理平台后端(之三) -- 多数据库通用分页
本期目标
之前我们的设计和实现,主要是围绕着查询展开的,今天我们的目标是补全平台的DDL、DML功能。
核心设计思路
架构分层职责如下:
| 姓名 | SELECT | DML | DDL |
|---|---|---|---|
| SqlSession | selectList() | executeDML() | executeDDL() |
| Executor | query() | dml() | ddl() |
| StatementHandler | query() | dml() | ddl() |
| ResultSetHandler | 处理ResultSet→List | 不涉及 | 不涉及 |
| 返回值 | List | int(影响行数) | boolean(执行成功/失败)或int |
完整类图
text
┌─────────────────────────────────────────────────────────────────────────────┐
│ SqlSession │
│ - configuration: Configuration │
│ - executor: Executor │
│ + selectList(sql, params): List<Map> ← SELECT │
│ + selectListWithPagination(sql, page, size): PageResult │
│ + executeDML(sql, params): int ← INSERT/UPDATE/DELETE│
│ + executeDDL(sql, params): int ← CREATE/ALTER/DROP │
│ + close(): void │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 持有
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Executor (接口) │
│ + query(sql, params): List<Map> ← SELECT │
│ + dml(sql, params): int ← INSERT/UPDATE/DELETE │
│ + ddl(sql, params): int ← CREATE/ALTER/DROP │
│ + close(): void │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 实现
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SimpleExecutor │
│ - configuration: Configuration │
│ - statementHandler: StatementHandler │
│ + query(sql, params): List<Map> ← 委托给StatementHandler │
│ + dml(sql, params): int ← 委托给StatementHandler │
│ + ddl(sql, params): int ← 委托给StatementHandler │
│ + close(): void │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 持有
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ StatementHandler (接口) │
│ + query(sql, params): List<Map> ← SELECT │
│ + dml(sql, params): int ← INSERT/UPDATE/DELETE │
│ + ddl(sql, params): int ← CREATE/ALTER/DROP │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 实现
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SimpleStatementHandler │
│ - configuration: Configuration │
│ - resultSetHandler: ResultSetHandler │
│ + query(sql, params): List<Map> ← 执行PreparedStatement │
│ + dml(sql, params): int ← 执行PreparedStatement │
│ + ddl(sql, params): int ← 执行PreparedStatement │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 使用
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ResultSetHandler (接口) │
│ + handleResultSets(rs): List<Map> ← 仅SELECT使用 │
└────────────────────────────────┬────────────────────────────────────────────┘
│ 实现
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ MapResultSetHandler │
│ + handleResultSets(rs): List<Map> │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Configuration │
│ - dataSource: DataSource │
│ - databaseType: DatabaseType │
│ - dialect: Dialect │
│ - defaultFetchSize: int ← 新增:默认查询抓取行数 │
│ - queryTimeout: int ← 新增:SQL执行超时秒数 │
│ + getDataSource(): DataSource │
│ + getDialect(): Dialect │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ SqlCommandType (枚举) │
│ SELECT, DML, DDL │
└─────────────────────────────────────────────────────────────────────────────┘
核心代码实现
1. SqlCommandType枚举
javaa
public enum SqlCommandType {
SELECT, // 查询:SELECT
DML, // 数据操纵:INSERT / UPDATE / DELETE
DDL; // 数据定义:CREATE / ALTER / DROP / TRUNCATE / RENAME
/**
* 根据SQL语句开头关键词自动判断类型
*/
public static SqlCommandType fromSql(String sql) {
if (sql == null || sql.trim().isEmpty()) {
return SELECT;
}
String upper = sql.trim().toUpperCase();
if (upper.startsWith("SELECT") || upper.startsWith("WITH") || upper.startsWith("SHOW")
|| upper.startsWith("DESC") || upper.startsWith("DESCRIBE") || upper.startsWith("EXPLAIN")) {
return SELECT;
}
if (upper.startsWith("INSERT") || upper.startsWith("UPDATE") || upper.startsWith("DELETE")
|| upper.startsWith("REPLACE") || upper.startsWith("MERGE")) {
return DML;
}
if (upper.startsWith("CREATE") || upper.startsWith("ALTER") || upper.startsWith("DROP")
|| upper.startsWith("TRUNCATE") || upper.startsWith("RENAME")
|| upper.startsWith("COMMENT") || upper.startsWith("ANALYZE")
|| upper.startsWith("OPTIMIZE") || upper.startsWith("REPAIR")) {
return DDL;
}
// 默认当成SELECT,实际执行时会报错
return SELECT;
}
}
2. 扩展Configuration(增加安全配置)
java
public class Configuration {
private DataSource dataSource;
private boolean cacheEnabled = false;
private DatabaseType databaseType;
private Dialect dialect;
// 新增:安全控制参数
private int defaultFetchSize = 10000; // 默认最大返回行数(防止OOM)
private int queryTimeout = 60; // SQL执行超时时间(秒)
private boolean ddlEnabled = true; // 是否允许执行DDL
private boolean dmlEnabled = true; // 是否允许执行DML
public Configuration(DataSource dataSource) {
this.dataSource = dataSource;
this.databaseType = detectDatabaseType();
this.dialect = createDialect();
}
// ... 原有方法 ...
// 新增getter/setter
public int getDefaultFetchSize() { return defaultFetchSize; }
public void setDefaultFetchSize(int defaultFetchSize) { this.defaultFetchSize = defaultFetchSize; }
public int getQueryTimeout() { return queryTimeout; }
public void setQueryTimeout(int queryTimeout) { this.queryTimeout = queryTimeout; }
public boolean isDdlEnabled() { return ddlEnabled; }
public void setDdlEnabled(boolean ddlEnabled) { this.ddlEnabled = ddlEnabled; }
public boolean isDmlEnabled() { return dmlEnabled; }
public void setDmlEnabled(boolean dmlEnabled) { this.dmlEnabled = dmlEnabled; }
}
3. Executor接口
java
public interface Executor {
// SELECT:返回结果集
List<Map<String, Object>> query(String sql, Object... parameters);
// DML(INSERT/UPDATE/DELETE):返回影响行数
int dml(String sql, Object... parameters);
// DDL(CREATE/ALTER/DROP等):返回0或影响行数
int ddl(String sql, Object... parameters);
// 分页查询(SELECT专用)
PageResult<Map<String, Object>> queryWithPagination(String sql, PageParam pageParam, Object... parameters);
void close();
}
4. SimpleExecutor实现
java
public class SimpleExecutor implements Executor {
private final Configuration configuration;
private final StatementHandler statementHandler;
public SimpleExecutor(Configuration configuration) {
this.configuration = configuration;
this.statementHandler = new SimpleStatementHandler(configuration);
}
@Override
public List<Map<String, Object>> query(String sql, Object... parameters) {
try {
return statementHandler.query(sql, parameters);
} catch (SQLException e) {
throw new RuntimeException("Error executing SELECT: " + sql, e);
}
}
@Override
public int dml(String sql, Object... parameters) {
// 检查是否允许执行DML
if (!configuration.isDmlEnabled()) {
throw new UnsupportedOperationException("DML operations are disabled in this configuration");
}
try {
return statementHandler.dml(sql, parameters);
} catch (SQLException e) {
throw new RuntimeException("Error executing DML: " + sql, e);
}
}
@Override
public int ddl(String sql, Object... parameters) {
// 检查是否允许执行DDL
if (!configuration.isDdlEnabled()) {
throw new UnsupportedOperationException("DDL operations are disabled in this configuration");
}
try {
return statementHandler.ddl(sql, parameters);
} catch (SQLException e) {
throw new RuntimeException("Error executing DDL: " + sql, e);
}
}
@Override
public PageResult<Map<String, Object>> queryWithPagination(String sql, PageParam pageParam, Object... parameters) {
try {
long total = statementHandler.executeCount(sql, parameters);
List<Map<String, Object>> rows = statementHandler.executePaged(
sql, pageParam.getOffset(), pageParam.getPageSize(), parameters
);
PageResult<Map<String, Object>> result = new PageResult<>();
result.setTotal(total);
result.setRows(rows);
return result;
} catch (SQLException e) {
throw new RuntimeException("Error executing paginated query: " + sql, e);
}
}
@Override
public void close() {
// 释放资源(如有)
}
}
5. StatementHandler接口
java
public interface StatementHandler {
// SELECT:查询并返回结果集
List<Map<String, Object>> query(String sql, Object... parameters) throws SQLException;
// DML(INSERT/UPDATE/DELETE):返回影响行数
int dml(String sql, Object... parameters) throws SQLException;
// DDL(CREATE/ALTER/DROP等):返回影响行数(通常为0)
int ddl(String sql, Object... parameters) throws SQLException;
// 以下两个方法供分页查询使用
long executeCount(String sql, Object... parameters) throws SQLException;
List<Map<String, Object>> executePaged(String sql, int offset, int limit, Object... parameters) throws SQLException;
}
6. SimpleStatementHandler完整实现
java
public class SimpleStatementHandler implements StatementHandler {
private final Configuration configuration;
private final ResultSetHandler resultSetHandler;
public SimpleStatementHandler(Configuration configuration) {
this.configuration = configuration;
this.resultSetHandler = new MapResultSetHandler();
}
// ==================== SELECT ====================
@Override
public List<Map<String, Object>> query(String sql, Object... parameters) throws SQLException {
try (Connection conn = configuration.getDataSource().getConnection();
PreparedStatement stmt = prepareStatement(conn, sql, parameters)) {
// 设置查询超时
stmt.setQueryTimeout(configuration.getQueryTimeout());
// 设置最大返回行数(防止OOM)
stmt.setMaxRows(configuration.getDefaultFetchSize());
try (ResultSet rs = stmt.executeQuery()) {
return resultSetHandler.handleResultSets(rs);
}
}
}
// ==================== DML(INSERT/UPDATE/DELETE) ====================
@Override
public int dml(String sql, Object... parameters) throws SQLException {
try (Connection conn = configuration.getDataSource().getConnection();
PreparedStatement stmt = prepareStatement(conn, sql, parameters)) {
stmt.setQueryTimeout(configuration.getQueryTimeout());
return stmt.executeUpdate();
}
}
// ==================== DDL(CREATE/ALTER/DROP等) ====================
@Override
public int ddl(String sql, Object... parameters) throws SQLException {
try (Connection conn = configuration.getDataSource().getConnection();
PreparedStatement stmt = prepareStatement(conn, sql, parameters)) {
stmt.setQueryTimeout(configuration.getQueryTimeout());
// DDL使用executeUpdate执行,返回0表示成功(某些数据库可能返回影响行数)
return stmt.executeUpdate();
}
}
// ==================== 分页查询支持 ====================
@Override
public long executeCount(String sql, Object... parameters) throws SQLException {
Dialect dialect = configuration.getDialect();
String countSql = dialect.getCountSql(sql);
try (Connection conn = configuration.getDataSource().getConnection();
PreparedStatement stmt = prepareStatement(conn, countSql, parameters)) {
stmt.setQueryTimeout(configuration.getQueryTimeout());
try (ResultSet rs = stmt.executeQuery()) {
return rs.next() ? rs.getLong(1) : 0L;
}
}
}
@Override
public List<Map<String, Object>> executePaged(String sql, int offset, int limit, Object... parameters) throws SQLException {
Dialect dialect = configuration.getDialect();
String pagedSql = dialect.getPagedSql(sql, offset, limit);
try (Connection conn = configuration.getDataSource().getConnection();
PreparedStatement stmt = prepareStatement(conn, pagedSql, parameters)) {
stmt.setQueryTimeout(configuration.getQueryTimeout());
stmt.setMaxRows(limit);
try (ResultSet rs = stmt.executeQuery()) {
return resultSetHandler.handleResultSets(rs);
}
}
}
// ==================== 私有工具方法 ====================
private PreparedStatement prepareStatement(Connection conn, String sql, Object... parameters) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(sql);
for (int i = 0; i < parameters.length; i++) {
stmt.setObject(i + 1, parameters[i]);
}
return stmt;
}
}
7. SqlSession(对外API)
java
public class SqlSession implements AutoCloseable {
private final Configuration configuration;
private final Executor executor;
public SqlSession(Configuration configuration) {
this.configuration = configuration;
this.executor = new SimpleExecutor(configuration);
}
// ==================== SELECT ====================
public List<Map<String, Object>> selectList(String sql, Object... parameters) {
return executor.query(sql, parameters);
}
public PageResult<Map<String, Object>> selectListWithPagination(String sql, int pageNum, int pageSize, Object... parameters) {
PageParam pageParam = new PageParam(pageNum, pageSize);
return executor.queryWithPagination(sql, pageParam, parameters);
}
// ==================== DML ====================
public int executeDML(String sql, Object... parameters) {
return executor.dml(sql, parameters);
}
// ==================== DDL ====================
public int executeDDL(String sql, Object... parameters) {
return executor.ddl(sql, parameters);
}
// ==================== 资源释放 ====================
@Override
public void close() {
executor.close();
}
public Configuration getConfiguration() {
return configuration;
}
}
8. ResultSetHandler
java
public interface ResultSetHandler {
List<Map<String, Object>> handleResultSets(ResultSet rs) throws SQLException;
}
public class MapResultSetHandler implements ResultSetHandler {
@Override
public List<Map<String, Object>> handleResultSets(ResultSet rs) throws SQLException {
List<Map<String, Object>> results = new ArrayList<>();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = rs.getObject(i);
row.put(columnName, value);
}
results.add(row);
}
return results;
}
}
使用示例
java
// ==================== 创建连接 ====================
DataSource dataSource = ...;
Configuration config = new Configuration(dataSource);
// 安全配置:限制查询返回行数、超时时间
config.setDefaultFetchSize(5000);
config.setQueryTimeout(30);
try (SqlSession session = new SqlSession(config)) {
// ==================== SELECT ====================
List<Map<String, Object>> users = session.selectList(
"SELECT id, name, age FROM user WHERE age > ?", 18
);
// 分页查询
PageResult<Map<String, Object>> page = session.selectListWithPagination(
"SELECT id, name FROM user ORDER BY id", 2, 20
);
System.out.println("总数: " + page.getTotal());
// ==================== DML ====================
int result = session.executeDML("INSERT INTO log(msg) VALUES(?)", "操作完成");
// ==================== DDL ====================
session.executeDDL("CREATE INDEX idx_name ON user(name)");
}
总结
至此,我们完成了一个平台该有的基本功能。