尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java内存数据库教学系统:手写SQL解析与HTML交互

Java内存数据库教学系统:手写SQL解析与HTML交互 简介这是一套面向Java初学者与数据库入门学习者的简易数据库系统开发实践源码聚焦基础CRUD操作与前后端协同实现适合课程设计、小型项目练手及数据库原理理解。资源共29个文件压缩包仅181KB包含13个Java核心类如Interpreter、Buffer_Manager、Record_Manager等实现数据库解析、缓存管理与记录操作11个XML配置文件支撑数据库连接、IDEA项目结构与IDE插件设置1个HTML前端界面提供基础交互入口1个SQL脚本完成表结构初始化与示例数据导入另有LICENSE、README及Idea项目配置文件保障可运行性与规范性。已有304人学习下载代码模块划分清晰、注释完整覆盖从SQL解析、索引管理到页面渲染的全流程配套markdown文档与测试用例便于快速编译调试与功能验证。1. 这不是“JavaHTML数据库”而是用纯Java内存引擎HTML前端模拟真实数据库交互的轻量级教学系统很多人看到“基于Java与HTML的简易数据库系统”第一反应是Java连MySQL都得靠JDBCHTML连后端都没有怎么搞数据库其实这个标题指向的是一类典型教学实践项目——它不依赖外部DBMS如MySQL、PostgreSQL而是用Java原生集合HashMap、ArrayList构建内存型数据表结构实现建表、插入、查询、更新、删除等核心SQL语义HTML则作为静态前端界面通过表单提交触发Java Servlet或嵌入式HTTP服务器完成CRUD操作。它解决的是数据库原理课中“概念落地难”的问题学生能亲手写出CREATE TABLE users(id INT, name VARCHAR(20))的解析逻辑也能在浏览器里输入SELECT * FROM users WHERE id1并看到结果。适合Java初学者巩固面向对象建模能力、Web基础开发者理解前后端数据流转、以及数据库课程设计中规避环境部署复杂度的场景。它不替代生产级数据库但能把《数据库系统概论》第六版里“关系模型”“查询处理”“事务ACID”等抽象概念变成可调试、可打断点、可修改源码的实体。2. 用Java内存表Servlet实现核心数据库功能从建表解析到SQL执行引擎2.1 为什么选内存表而非嵌入式数据库——教学场景下的三重取舍逻辑在真实项目中H2、SQLite或Derby是更自然的选择但本项目坚持纯Java手写核心原因有三点第一教学穿透性——使用H2时学生调用connection.createStatement().execute(CREATE TABLE...)就结束了无法观察词法分析、语法树构建、执行计划生成等内部环节第二依赖极简性——仅需JDK 8和标准Servlet API避免Maven引入冲突、驱动版本错配等干扰项符合“java基础”“java安装”等热词指向的入门友好诉求第三调试可见性——所有表结构、索引、查询逻辑都在.java文件中学生可在IDE中逐行跟踪parseCreateTable(CREATE TABLE t(a INT, b VARCHAR(10)))如何拆解字段名、类型、长度并映射为ListColumn对象。这种“透明性”正是“数据库系统概论课本答案哪里找”背后的真实需求答案不在书后习题解析里而在自己写的TableManager.java第47行。2.2 建表语句解析器用正则分段状态机处理SQL DDL建表是整个系统的起点其解析质量直接决定后续查询能否正确执行。我们不采用ANTLR等重型工具而是用Java原生正则配合状态机兼顾可读性与教学价值// CreateTableParser.java public class CreateTableParser { // 匹配 CREATE TABLE table_name (col1 type1, col2 type2, ...) 格式 private static final String CREATE_TABLE_PATTERN CREATE\\sTABLE\\s(\\w)\\s*\\(([^)])\\); public Table parse(String sql) { Pattern pattern Pattern.compile(CREATE_TABLE_PATTERN, Pattern.CASE_INSENSITIVE); Matcher matcher pattern.matcher(sql.trim()); if (!matcher.find()) { throw new IllegalArgumentException(Invalid CREATE TABLE syntax: sql); } String tableName matcher.group(1); String columnsDef matcher.group(2); ListColumn columns parseColumns(columnsDef); return new Table(tableName, columns); } private ListColumn parseColumns(String columnsDef) { ListColumn columns new ArrayList(); // 按逗号分割字段定义但需跳过括号内的逗号如 VARCHAR(20) String[] columnParts splitByCommaOutsideParentheses(columnsDef); for (String part : columnParts) { String[] tokens part.trim().split(\\s, 2); // 字段名 类型定义 if (tokens.length 2) continue; String columnName tokens[0]; String typeDef tokens[1].trim(); Column.Type type parseColumnType(typeDef); Integer length parseTypeLength(typeDef); columns.add(new Column(columnName, type, length)); } return columns; } private Column.Type parseColumnType(String typeDef) { if (typeDef.toUpperCase().startsWith(INT)) return Column.Type.INT; if (typeDef.toUpperCase().startsWith(VARCHAR)) return Column.Type.VARCHAR; if (typeDef.toUpperCase().startsWith(CHAR)) return Column.Type.CHAR; throw new IllegalArgumentException(Unsupported type: typeDef); } private Integer parseTypeLength(String typeDef) { // 提取 VARCHAR(20) 中的 20 Pattern lenPattern Pattern.compile(\\((\\d)\\)); Matcher lenMatcher lenPattern.matcher(typeDef); return lenMatcher.find() ? Integer.parseInt(lenMatcher.group(1)) : null; } private String[] splitByCommaOutsideParentheses(String input) { // 手动状态机遇到 ( 计数1) 计数-1仅在计数为0时按逗号切分 ListString parts new ArrayList(); StringBuilder current new StringBuilder(); int parenCount 0; for (char c : input.toCharArray()) { if (c () parenCount; else if (c )) parenCount--; else if (c , parenCount 0) { parts.add(current.toString().trim()); current.setLength(0); continue; } current.append(c); } if (current.length() 0) parts.add(current.toString().trim()); return parts.toArray(new String[0]); } }提示此解析器刻意避开复杂SQL如约束、主键聚焦CREATE TABLE最简形态。splitByCommaOutsideParentheses方法是关键——它用整数计数模拟括号匹配比正则递归更易理解也呼应了“java面试八股文”中常考的手动括号匹配算法题。2.3 内存表执行引擎用HashMap实现索引加速用Stream API完成WHERE过滤表数据存储在MapString, Object的List中每行一个Map但查询性能不能只靠遍历。我们为每个表维护一个MapString, MapObject, ListInteger结构即“列名→值→行号列表”的二级索引// Table.java public class Table { private final String name; private final ListColumn columns; private final ListMapString, Object rows new ArrayList(); // 索引列名 → (值 → 行索引列表) private final MapString, MapObject, ListInteger indexes new HashMap(); public void insert(MapString, Object row) { int rowIndex rows.size(); rows.add(row); // 更新索引对每个有索引的列将当前行号加入对应值的列表 for (String colName : getIndexedColumnNames()) { Object value row.get(colName); indexes.computeIfAbsent(colName, k - new HashMap()) .computeIfAbsent(value, v - new ArrayList()) .add(rowIndex); } } public ListMapString, Object select(ListString columns, String whereClause) { ListInteger matchedRows new ArrayList(); if (whereClause ! null !whereClause.trim().isEmpty()) { // 解析 WHERE nameAlice 或 id5 WhereCondition condition WhereCondition.parse(whereClause); String colName condition.getColumnName(); Object value condition.getValue(); String operator condition.getOperator(); // 利用索引快速定位仅当条件列有索引且为等值查询时启用 if (.equals(operator) indexes.containsKey(colName)) { ListInteger indexRows indexes.get(colName).getOrDefault(value, Collections.emptyList()); matchedRows.addAll(indexRows); } else { // 全表扫描 for (int i 0; i rows.size(); i) { MapString, Object row rows.get(i); if (condition.matches(row)) { matchedRows.add(i); } } } } else { // 无WHERE返回全部行索引 for (int i 0; i rows.size(); i) matchedRows.add(i); } // 构建结果集按需投影指定列 ListMapString, Object result new ArrayList(); for (Integer rowIndex : matchedRows) { MapString, Object row rows.get(rowIndex); MapString, Object projected new HashMap(); for (String col : columns) { projected.put(col, row.get(col)); } result.add(projected); } return result; } // WhereCondition.java 简化版支持 , , , , , ! public static class WhereCondition { private final String columnName; private final String operator; private final Object value; public static WhereCondition parse(String clause) { // 粗略解析只处理单条件如 id 100 String[] parts clause.trim().split(\\s, 3); if (parts.length 3) throw new IllegalArgumentException(Invalid WHERE clause); return new WhereCondition(parts[0], parts[1], parseValue(parts[2])); } private static Object parseValue(String val) { // 去除单引号尝试转为数字 String clean val.replace(, ); try { return Integer.parseInt(clean); } catch (NumberFormatException e) { return clean; } } public boolean matches(MapString, Object row) { Object actual row.get(columnName); if (actual null) return false; if (.equals(operator)) return Objects.equals(actual, value); if (.equals(operator)) return ((Number) actual).doubleValue() ((Number) value).doubleValue(); if (.equals(operator)) return ((Number) actual).doubleValue() ((Number) value).doubleValue(); // 其他操作符类似... return false; } } }注意索引更新在insert()中同步完成保证查询一致性select()方法明确区分“索引加速路径”和“全表扫描路径”让学生直观理解B树索引为何只对等值查询高效。WhereCondition.parse()虽简化但已覆盖“java基础”中字符串分割、类型转换等核心技能点。3. HTML前端与Servlet通信用标准表单提交实现零JavaScript的数据库操作界面3.1 HTML页面结构遵循!doctype html 规范用语义化标签组织操作区前端不依赖任何框架完全基于原生HTMLCSS确保“html网页制作”“html➕css➕js基础语法”学习者能直接复用。关键在于表单action属性精准指向Servlet路径且methodpost保证数据可被Java后端接收!-- index.html -- !doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title简易数据库系统/title style body { font-family: Segoe UI, sans-serif; margin: 2rem; } .form-group { margin: 1rem 0; } label { display: inline-block; width: 120px; font-weight: bold; } input[typetext], textarea { width: 300px; padding: 6px; } button { background: #007bff; color: white; border: none; padding: 8px 16px; cursor: pointer; } table { border-collapse: collapse; margin-top: 1rem; } th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; } th { background-color: #f2f2f2; } /style /head body h1简易数据库系统/h1 !-- 建表表单 -- div classform-group h2创建表/h2 form action/create-table methodpost label forcreateSql建表语句/label input typetext idcreateSql namesql placeholderCREATE TABLE users(id INT, name VARCHAR(20)) required button typesubmit执行/button /form /div !-- 插入数据表单 -- div classform-group h2插入数据/h2 form action/insert methodpost label fortableName表名/label input typetext idtableName nametable required br label forinsertData数据JSON格式/label textarea idinsertData namedata rows2 placeholder{id:1,name:张三}{id:1,name:张三}/textarea button typesubmit插入/button /form /div !-- 查询表单 -- div classform-group h2查询数据/h2 form action/select methodpost label forselectTable表名/label input typetext idselectTable nametable required br label forcolumns查询列逗号分隔/label input typetext idcolumns namecolumns value* placeholder* 或 id,name br label forwhereClauseWHERE条件/label input typetext idwhereClause namewhere placeholderid1 或 name李四 button typesubmit查询/button /form /div !-- 查询结果显示区 -- div idresult h2查询结果/h2 !-- 结果由Servlet渲染后填充 -- p执行查询后显示此处/p /div /body /html提示meta charsetutf-8和meta nameviewport是现代HTML标准必备直接回应“!doctype htmlmeta name”等高频搜索片段表单name属性如namesql必须与Servlet中request.getParameter(sql)严格一致这是前后端通信的契约。3.2 Servlet路由设计用单一入口分发不同SQL操作避免代码重复为降低复杂度所有请求统一由DatabaseServlet处理通过request.getRequestURI()后缀判断操作类型。这种设计比为每个功能建独立Servlet更易维护也符合“java server pagespics”中JSPServlet协作的常见模式// DatabaseServlet.java WebServlet(/database/*) public class DatabaseServlet extends HttpServlet { private final DatabaseManager dbManager new DatabaseManager(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo request.getPathInfo(); // 获取 /create-table, /insert 等 response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); try { if (/create-table.equals(pathInfo)) { handleCreateTable(request, out); } else if (/insert.equals(pathInfo)) { handleInsert(request, out); } else if (/select.equals(pathInfo)) { handleSelect(request, out); } else if (/update.equals(pathInfo)) { handleUpdate(request, out); } else if (/delete.equals(pathInfo)) { handleDelete(request, out); } else { out.println(p未知操作: pathInfo /p); } } catch (Exception e) { out.println(p stylecolor:red;错误: e.getMessage() /p); e.printStackTrace(); // 开发期保留上线应记录日志 } } private void handleCreateTable(HttpServletRequest request, PrintWriter out) { String sql request.getParameter(sql); if (sql null || sql.trim().isEmpty()) { out.println(p建表语句不能为空/p); return; } try { Table table new CreateTableParser().parse(sql); dbManager.createTable(table); out.println(p✅ 表 table.getName() 创建成功/p); } catch (Exception e) { out.println(p stylecolor:red;❌ 创建失败: e.getMessage() /p); } } private void handleInsert(HttpServletRequest request, PrintWriter out) { String tableName request.getParameter(table); String jsonData request.getParameter(data); if (tableName null || jsonData null) { out.println(p表名和数据不能为空/p); return; } try { MapString, Object data new ObjectMapper().readValue(jsonData, Map.class); dbManager.insert(tableName, data); out.println(p✅ 数据插入成功/p); } catch (Exception e) { out.println(p stylecolor:red;❌ 插入失败: e.getMessage() /p); } } private void handleSelect(HttpServletRequest request, PrintWriter out) { String tableName request.getParameter(table); String columnsParam request.getParameter(columns); String whereClause request.getParameter(where); if (tableName null) { out.println(p表名不能为空/p); return; } ListString columns *.equals(columnsParam) ? dbManager.getTableColumns(tableName) : Arrays.asList(columnsParam.split(\\s*,\\s*)); try { ListMapString, Object results dbManager.select(tableName, columns, whereClause); renderResultTable(out, results, columns); } catch (Exception e) { out.println(p stylecolor:red;❌ 查询失败: e.getMessage() /p); } } private void renderResultTable(PrintWriter out, ListMapString, Object results, ListString columns) { if (results.isEmpty()) { out.println(p未查询到数据/p); return; } out.println(table); out.println(tr); for (String col : columns) { out.println(th col /th); } out.println(/tr); for (MapString, Object row : results) { out.println(tr); for (String col : columns) { out.println(td String.valueOf(row.get(col)) /td); } out.println(/tr); } out.println(/table); } }注意ObjectMapper来自Jackson库需在pom.xml添加com.fasterxml.jackson.core:jackson-databind用于解析JSON格式插入数据这比手动split(,)更健壮也覆盖“java面试大全及答案”中常问的JSON处理方案。renderResultTable方法直接输出HTML表格避免前端JS渲染确保“零JavaScript”目标达成。4. 部署与验证用Tomcat 9运行系统用curl命令行快速测试接口4.1 Tomcat部署步骤从war包构建到服务启动的完整链路本系统最终以WAR包形式部署到Tomcat这是Java Web开发的标准流程也是“java下载安装”“java安装”后最常接触的服务器。关键步骤如下项目结构标准化确保src/main/webapp/下包含index.html和WEB-INF/web.xml若用注解可省略src/main/java/存放所有.java文件Maven打包在pom.xml中声明maven-war-plugin执行mvn clean package生成target/simple-db.warTomcat配置将simple-db.war复制到$CATALINA_HOME/webapps/目录启动Tomcatbin/startup.sh或bin/startup.bat访问验证浏览器打开http://localhost:8080/simple-db/即可看到首页。提示若遇404检查WAR包是否自动解压为simple-db/目录若遇500错误查看logs/catalina.out中ClassNotFoundException确认jackson-databind等依赖已打入WAR包maven-war-plugin默认会包含WEB-INF/lib/。4.2 用curl进行接口级验证绕过浏览器直击Servlet逻辑前端页面是给用户用的而curl命令是开发者验证后端逻辑的利器。以下命令可快速测试各功能点无需启动浏览器# 1. 创建表 curl -X POST http://localhost:8080/simple-db/database/create-table \ -H Content-Type: application/x-www-form-urlencoded \ -d sqlCREATE TABLE students(id INT, name VARCHAR(20), score INT) # 2. 插入数据注意JSON需URL编码 curl -X POST http://localhost:8080/simple-db/database/insert \ -H Content-Type: application/x-www-form-urlencoded \ -d tablestudents \ -d data%7B%22id%22%3A1%2C%22name%22%3A%22张三%22%2C%22score%22%3A85%7D # 3. 查询全部数据 curl -X POST http://localhost:8080/simple-db/database/select \ -H Content-Type: application/x-www-form-urlencoded \ -d tablestudents \ -d columns* # 4. 带WHERE条件查询 curl -X POST http://localhost:8080/simple-db/database/select \ -H Content-Type: application/x-www-form-urlencoded \ -d tablestudents \ -d columnsname,score \ -d wherescore%3E80 # score80 的URL编码注意-d参数中的data值需URL编码%7B%22id%22%3A1...即{id:1...}的编码结果可用在线工具或python -c import urllib.parse; print(urllib.parse.quote({id:1}))生成。这些命令直接调用Servlet返回HTML片段是排查“数据库系统原理”中数据一致性问题的最快路径。5. 进阶技巧添加事务支持与导出CSV功能让教学系统更贴近真实场景5.1 用ThreadLocal实现简易事务解决并发插入时的数据不一致问题当前系统在多用户同时操作时存在风险比如两个请求同时向同一表插入数据rows.size()可能被并发读取导致行号冲突。教学系统虽不追求高并发但加入事务概念能深化对“数据库系统概论第六版”中ACID的理解。我们用ThreadLocal为每个请求绑定独立的“待提交变更集”在commit()时原子性应用// TransactionManager.java public class TransactionManager { private static final ThreadLocalTransaction currentTransaction ThreadLocal.withInitial(Transaction::new); public static void begin() { // 每个线程初始化空事务 } public static void commit() { Transaction tx currentTransaction.get(); tx.applyToDatabase(); // 将insert/delete/update操作批量应用到内存表 currentTransaction.remove(); } public static void rollback() { currentTransaction.remove(); } public static void insert(String table, MapString, Object data) { currentTransaction.get().addInsert(table, data); } // Transaction内部类简化版 public static class Transaction { private final MapString, ListMapString, Object pendingInserts new HashMap(); public void addInsert(String table, MapString, Object data) { pendingInserts.computeIfAbsent(table, k - new ArrayList()).add(data); } public void applyToDatabase() { DatabaseManager db DatabaseManager.getInstance(); for (Map.EntryString, ListMapString, Object entry : pendingInserts.entrySet()) { String table entry.getKey(); for (MapString, Object data : entry.getValue()) { db.insert(table, data); // 调用原有insert方法 } } } } }在DatabaseServlet中包裹业务逻辑private void handleInsert(HttpServletRequest request, PrintWriter out) { TransactionManager.begin(); try { String tableName request.getParameter(table); String jsonData request.getParameter(data); MapString, Object data new ObjectMapper().readValue(jsonData, Map.class); TransactionManager.insert(tableName, data); TransactionManager.commit(); out.println(p✅ 事务提交成功/p); } catch (Exception e) { TransactionManager.rollback(); out.println(p stylecolor:red;❌ 事务回滚: e.getMessage() /p); } }提示ThreadLocal确保每个HTTP请求对应一个线程拥有独立事务上下文这是理解SpringTransactional底层机制的起点也回应了“java动态代理”等面试高频点——代理模式正是为了解耦事务管理与业务逻辑。5.2 导出CSV功能用Response流生成可下载文件强化Web响应控制能力教学系统常需将查询结果导出为CSV供Excel分析。这要求Servlet不返回HTML而是设置Content-Disposition头触发浏览器下载private void handleExportCsv(HttpServletRequest request, HttpServletResponse response) throws IOException { String tableName request.getParameter(table); String whereClause request.getParameter(where); ListMapString, Object results dbManager.select(tableName, dbManager.getTableColumns(tableName), whereClause); response.setContentType(text/csv;charsetUTF-8); response.setHeader(Content-Disposition, attachment; filename tableName .csv); PrintWriter csvOut response.getWriter(); // 写入CSV头部 ListString columns dbManager.getTableColumns(tableName); csvOut.print(String.join(,, columns)); csvOut.println(); // 写入数据行 for (MapString, Object row : results) { ListString values new ArrayList(); for (String col : columns) { Object val row.get(col); String strVal val null ? : String.valueOf(val); // CSV转义含逗号、换行、双引号的字段用双引号包裹双引号本身转义为 if (strVal.contains(,) || strVal.contains(\n) || strVal.contains(\)) { strVal \ strVal.replace(\, \\) \; } values.add(strVal); } csvOut.print(String.join(,, values)); csvOut.println(); } }在index.html中添加导出按钮div classform-group h2导出CSV/h2 form action/export-csv methodpost label forexportTable表名/label input typetext idexportTable nametable required br label forexportWhereWHERE条件可选/label input typetext idexportWhere namewhere placeholderscore90 button typesubmit导出/button /form /div注意CSV转义规则双引号内双引号需转义为两个双引号是“html格式转换wps表格”等实际场景的关键细节此处实现可直接用于生产环境。导出功能让系统从“演示工具”升级为“实用工具”真正覆盖“数据库系统原理及应用”中的数据交换需求。本文还有配套的精品资源点击获取
返回列表