【设计】设计一个web版的数据库管理平台后端(之四) -- 补全DDL、DML功能

前情回顾

【设计】设计一个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)");

}

总结

至此,我们完成了一个平台该有的基本功能。