前言:
由于前段時間,項目組長分配的任務是要完成一個在線編寫 SQL 并要實現(xiàn)查詢功能的需求,最終需要將查詢到的數(shù)據(jù)以 JSON 格式顯示到響應數(shù)據(jù)的區(qū)域,以供操作者進行查看,一開始拿到需求時想著直接使用 Mybatis 進行操作不就可以了,完全沒必要大費周章,因為在 MyBatis 中有個拼接 SQL 的語法,可以使用 ${sql} 來進行執(zhí)行輸入的 SQL 語句,但是實際操作起來并不是想象中的那么簡單,因為使用 MyBatis 會將數(shù)據(jù)源固定在本項目所使用的數(shù)據(jù)庫,而不可以進行數(shù)據(jù)源之間的切換,無法進行其他數(shù)據(jù)源中表的查詢操作 :x:,所以在實現(xiàn)過程也是相當艱難曲折…
難度分析
在線執(zhí)行 SQL 語句的查詢主要的難點分為以下幾點:其一則是對 JDBC 的部分的 API 理解的不夠透徹,導致在實現(xiàn)某些工鞥是并不是想象中的順利;其二在于對于查詢部分屬性的SQL語句該如何使用 Java 進行實現(xiàn),是將輸入的字符串進行分割再拼接還是使用直接整條語句的查詢操作;其三則是用戶可以動態(tài)的切換數(shù)據(jù)源,并且對相應數(shù)據(jù)源下的表進行查詢操作,如果使用 Mybatis 進行 SQL 的查詢操作則無法進行數(shù)據(jù)源的切換,只能查詢所在微服務項目所連接的數(shù)據(jù)庫進行查詢,否則無法進行相應的操作,即會出現(xiàn)該數(shù)據(jù)庫下并不存在所查詢的某張數(shù)據(jù)庫表的錯誤信息 :x:
項目回顧(在線編寫SQL查詢)
需求分析
使用者在頁面可以選擇切換需要操作的數(shù)據(jù)源,并在編輯區(qū)域內(nèi)輸入 SQL 語句進行查詢,查詢語句可以是全表查詢表中所有字段或者根據(jù)所需查詢指定某幾列字段對應的值,點擊查詢之后即可在響應數(shù)據(jù)之后以 JSON 的格式將查詢到的每一條數(shù)據(jù)封裝一個對象最后顯示到響應數(shù)據(jù)區(qū)域以供操作者進行觀看以達到可視化工具的效果。
項目搭建
① 引入項目所需要的相關(guān)Maven依賴
org.springframework.boot spring-boot-starter-web commons-lang commons-lang 2.6 javax.servlet servlet-api 2.5 provided mysql mysql-connector-java 8.0.28
② 編寫配置文件
server: port: 8080spring: datasource: username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/data_source?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
③ 創(chuàng)建Controller前端控制器
@PostMapping(“/dynamic-query”)@ApiOperation(value = “動態(tài)執(zhí)行SQL”)public Result dynamicQuery(@RequestBody DynamicQuery dynamicQuery){ List res = null;BladeVisualDb visualDb = bladeVisualDbService.getOne(new QueryWrapper().eq(“id”, dynamicQuery.getId()));List result = ConnectBySql.connAndExecSql(visualDb.getUrl(), visualDb.getDriverClass(), visualDb.getUsername(), visualDb.getPassword(),dynamicQuery.getSql());return Result.ok(result);}
④ 探究 JDBC中ResultSet中的方法
- next()
解釋:將光標從其當前位置向前移動一行。 ResultSet 游標最初位于第一行之前;第一次調(diào)用 next 方法使第一行成為當前行;第二次調(diào)用使第二行成為當前行,依此類推。實則對查詢到的數(shù)據(jù)進行遍歷操作
- getString(String columnLabel)
解釋:根據(jù)表中對應的列名稱來查詢到該列對應到的數(shù)據(jù)
- getMetaData()
解釋:獲取某張表中列的數(shù)量、類型和列名稱
- findColumn(String columnLabel)
解釋:返回某個列的索引值即下標
⑤ 獲取所有的數(shù)據(jù)源
因為使用者是可以動態(tài)的選擇數(shù)據(jù)源從而來對表中的數(shù)據(jù)進行相關(guān)操作,所以首先要查詢出可供連接的數(shù)據(jù)源
/** * 下拉數(shù)據(jù)源列表 * @return */@ApiOperation(value = “下拉列表”)@GetMapping(“/db-list”)public Result list(){ List bladeVisualDbs = bladeVisualDbService.list();return Result.ok(bladeVisualDbs);}
⑥ 實現(xiàn)執(zhí)行查詢邏輯
首先執(zhí)行查詢有兩種方式:所有字段的全查詢即 select * from xx 或者 查詢指定字段即 select xx from xx
- 所有字段的全查詢即 select * from xx
在進行所有字段的查詢時,由于無法得知需要查詢的表中有什么字段,所以首先需要對輸入的 SQL 字符串進行判斷是否是全字段查詢,然后即可獲取查詢表中的所有字段,然后再一一的進行查詢出字段對應的值即可,此操作也是需要對輸入 SQL 字符串進行分割的,拿出所有插敘的表名即可。
ResultSet resultSet = stmt.executeQuery(sql);//如果輸入的SQL屬于select * 操作if (sql.contains(“*”)){ List list = new ArrayList(); //獲取SQL中需要查詢的表的結(jié)構(gòu) ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i = 1; i {}”,metaData.getColumnName(i)); //獲取表中的所有列 list.add(metaData.getColumnName(i)); } while (resultSet.next()){ HashMap hashMap = new HashMap(); for (String index : list) { //根據(jù)列名稱查詢出該列對應的數(shù)據(jù) String rs = resultSet.getString(index); //將其放入列和值存放HashMap中 hashMap.put(index,rs); //將多個HashMap合并成一個Map hashMap.putAll(hashMap); } res.add(hashMap); }}
- 查詢指定字段即 select xx from xx
在使用 JDBC 進行指定字段查詢時需要對輸入的 SQL 字符串進行分割后將所需要查詢到的字段再使用 JDBC 中的 getString(String columnName) 進行查詢字段對應的值
/** * 將SQL語句進行拆分 * @param sql * @return */ @NotNull private static List getString(String sql) { List list = new ArrayList(); String str = sql.substring(0, sql.indexOf(“from”)); String realSql = str.replace(“select”, “”).trim(); if (realSql.contains(“,”)){ String[] split = realSql.split(“,”); for (String s : split) { list.add(s); } }else { list.add(realSql); } return list; }
隨后將分割完成的 SQL 存放到 List 集合中再進行查詢操作
ResultSet resultSet = stmt.executeQuery(sql);while (resultSet.next()){ HashMap hashMap = new HashMap(); List str = getString(sql); for (String index : str) { String rs = resultSet.getString(index); hashMap.put(index,rs); hashMap.putAll(hashMap); } res.add(hashMap);}
經(jīng)過對操作者輸入的 SQL 即可完成對表的查詢操作,由于所要實現(xiàn)的是可動態(tài)切換數(shù)據(jù)源從而進行相關(guān)的查詢操作,所以在此操作邏輯中首先需要連接數(shù)據(jù)庫,后再對輸入的 SQL 進行分割查詢等操作。合并后的完整代碼:
/** * 連接數(shù)據(jù)庫并根據(jù)輸入的SQL語句查詢數(shù)據(jù) * @param url * @param driverClass * @param username * @param password * @param sql * @return */ public static List connAndExecSql(String url, String driverClass, String username, String password, String sql) { Boolean result = false; Connection conn = null; Statement stmt = null; List res = new ArrayList(); try { Class.forName(driverClass); System.out.println(“————連接數(shù)據(jù)庫———–“); conn = DriverManager.getConnection(url,username,password); stmt = conn.createStatement(); ResultSet resultSet = stmt.executeQuery(sql); //如果輸入的SQL屬于select * 操作 if (sql.contains(“*”)){ List list = new ArrayList(); //獲取SQL中需要查詢的表的結(jié)構(gòu) ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i = 1; i {}”,metaData.getColumnName(i)); //獲取表中的所有列 list.add(metaData.getColumnName(i)); } while (resultSet.next()){ HashMap hashMap = new HashMap(); for (String index : list) { //根據(jù)列名稱查詢出該列對應的數(shù)據(jù) String rs = resultSet.getString(index); //將其放入列和值存放HashMap中 hashMap.put(index,rs); //將多個HashMap合并成一個Map hashMap.putAll(hashMap); } res.add(hashMap); } }else { while (resultSet.next()){ HashMap hashMap = new HashMap(); List str = getString(sql); for (String index : str) { String rs = resultSet.getString(index); hashMap.put(index,rs); hashMap.putAll(hashMap); } res.add(hashMap); } } //完成連接數(shù)據(jù)庫 stmt.close(); conn.close(); System.out.println(“查詢連接結(jié)束”); } catch (ClassNotFoundException e) { e.printStackTrace(); }catch (SQLException e){ e.printStackTrace(); } return res; } /** * 將SQL語句進行拆分 * @param sql * @return */ @NotNull private static List getString(String sql) { List list = new ArrayList(); String str = sql.substring(0, sql.indexOf(“from”)); String realSql = str.replace(“select”, “”).trim(); if (realSql.contains(“,”)){ String[] split = realSql.split(“,”); for (String s : split) { list.add(s); } }else { list.add(realSql); } return list; }
⑦ Vue前端引入monaco-editor組件進行編寫SQL語句
項目總結(jié)
在使用 JDBC 來進行數(shù)據(jù)庫的操作,在使用時由于對其方法并不是全部了解,所以需要邊看源碼邊改善功能,因此遇到問題時才會感覺到腦中知識儲備的不足,目前只是實現(xiàn)了查詢操作,還未完善 CRUD 全部過程的操作。