前言:
由于前段時(shí)間,項(xiàng)目組長(zhǎng)分配的任務(wù)是要完成一個(gè)在線編寫 SQL 并要實(shí)現(xiàn)查詢功能的需求,最終需要將查詢到的數(shù)據(jù)以 JSON 格式顯示到響應(yīng)數(shù)據(jù)的區(qū)域,以供操作者進(jìn)行查看,一開始拿到需求時(shí)想著直接使用 Mybatis 進(jìn)行操作不就可以了,完全沒必要大費(fèi)周章,因?yàn)樵?MyBatis 中有個(gè)拼接 SQL 的語(yǔ)法,可以使用 ${sql} 來進(jìn)行執(zhí)行輸入的 SQL 語(yǔ)句,但是實(shí)際操作起來并不是想象中的那么簡(jiǎn)單,因?yàn)槭褂?MyBatis 會(huì)將數(shù)據(jù)源固定在本項(xiàng)目所使用的數(shù)據(jù)庫(kù),而不可以進(jìn)行數(shù)據(jù)源之間的切換,無法進(jìn)行其他數(shù)據(jù)源中表的查詢操作 :x:,所以在實(shí)現(xiàn)過程也是相當(dāng)艱難曲折…
難度分析
在線執(zhí)行 SQL 語(yǔ)句的查詢主要的難點(diǎn)分為以下幾點(diǎn):其一則是對(duì) JDBC 的部分的 API 理解的不夠透徹,導(dǎo)致在實(shí)現(xiàn)某些工鞥是并不是想象中的順利;其二在于對(duì)于查詢部分屬性的SQL語(yǔ)句該如何使用 Java 進(jìn)行實(shí)現(xiàn),是將輸入的字符串進(jìn)行分割再拼接還是使用直接整條語(yǔ)句的查詢操作;其三則是用戶可以動(dòng)態(tài)的切換數(shù)據(jù)源,并且對(duì)相應(yīng)數(shù)據(jù)源下的表進(jìn)行查詢操作,如果使用 Mybatis 進(jìn)行 SQL 的查詢操作則無法進(jìn)行數(shù)據(jù)源的切換,只能查詢所在微服務(wù)項(xiàng)目所連接的數(shù)據(jù)庫(kù)進(jìn)行查詢,否則無法進(jìn)行相應(yīng)的操作,即會(huì)出現(xiàn)該數(shù)據(jù)庫(kù)下并不存在所查詢的某張數(shù)據(jù)庫(kù)表的錯(cuò)誤信息 :x:
項(xiàng)目回顧(在線編寫SQL查詢)
需求分析
使用者在頁(yè)面可以選擇切換需要操作的數(shù)據(jù)源,并在編輯區(qū)域內(nèi)輸入 SQL 語(yǔ)句進(jìn)行查詢,查詢語(yǔ)句可以是全表查詢表中所有字段或者根據(jù)所需查詢指定某幾列字段對(duì)應(yīng)的值,點(diǎn)擊查詢之后即可在響應(yīng)數(shù)據(jù)之后以 JSON 的格式將查詢到的每一條數(shù)據(jù)封裝一個(gè)對(duì)象最后顯示到響應(yīng)數(shù)據(jù)區(qū)域以供操作者進(jìn)行觀看以達(dá)到可視化工具的效果。
項(xiàng)目搭建
① 引入項(xiàng)目所需要的相關(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 = “動(dòng)態(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()
解釋:將光標(biāo)從其當(dāng)前位置向前移動(dòng)一行。 ResultSet 游標(biāo)最初位于第一行之前;第一次調(diào)用 next 方法使第一行成為當(dāng)前行;第二次調(diào)用使第二行成為當(dāng)前行,依此類推。實(shí)則對(duì)查詢到的數(shù)據(jù)進(jìn)行遍歷操作
- getString(String columnLabel)
解釋:根據(jù)表中對(duì)應(yīng)的列名稱來查詢到該列對(duì)應(yīng)到的數(shù)據(jù)
- getMetaData()
解釋:獲取某張表中列的數(shù)量、類型和列名稱
- findColumn(String columnLabel)
解釋:返回某個(gè)列的索引值即下標(biāo)
⑤ 獲取所有的數(shù)據(jù)源
因?yàn)槭褂谜呤强梢詣?dòng)態(tài)的選擇數(shù)據(jù)源從而來對(duì)表中的數(shù)據(jù)進(jìn)行相關(guān)操作,所以首先要查詢出可供連接的數(shù)據(jù)源
/** * 下拉數(shù)據(jù)源列表 * @return */@ApiOperation(value = “下拉列表”)@GetMapping(“/db-list”)public Result list(){ List bladeVisualDbs = bladeVisualDbService.list();return Result.ok(bladeVisualDbs);}
⑥ 實(shí)現(xiàn)執(zhí)行查詢邏輯
首先執(zhí)行查詢有兩種方式:所有字段的全查詢即 select * from xx 或者 查詢指定字段即 select xx from xx
- 所有字段的全查詢即 select * from xx
在進(jìn)行所有字段的查詢時(shí),由于無法得知需要查詢的表中有什么字段,所以首先需要對(duì)輸入的 SQL 字符串進(jìn)行判斷是否是全字段查詢,然后即可獲取查詢表中的所有字段,然后再一一的進(jìn)行查詢出字段對(duì)應(yīng)的值即可,此操作也是需要對(duì)輸入 SQL 字符串進(jìn)行分割的,拿出所有插敘的表名即可。
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ù)列名稱查詢出該列對(duì)應(yīng)的數(shù)據(jù) String rs = resultSet.getString(index); //將其放入列和值存放HashMap中 hashMap.put(index,rs); //將多個(gè)HashMap合并成一個(gè)Map hashMap.putAll(hashMap); } res.add(hashMap); }}
- 查詢指定字段即 select xx from xx
在使用 JDBC 進(jìn)行指定字段查詢時(shí)需要對(duì)輸入的 SQL 字符串進(jìn)行分割后將所需要查詢到的字段再使用 JDBC 中的 getString(String columnName) 進(jìn)行查詢字段對(duì)應(yīng)的值
/** * 將SQL語(yǔ)句進(jìn)行拆分 * @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 集合中再進(jìn)行查詢操作
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)過對(duì)操作者輸入的 SQL 即可完成對(duì)表的查詢操作,由于所要實(shí)現(xiàn)的是可動(dòng)態(tài)切換數(shù)據(jù)源從而進(jìn)行相關(guān)的查詢操作,所以在此操作邏輯中首先需要連接數(shù)據(jù)庫(kù),后再對(duì)輸入的 SQL 進(jìn)行分割查詢等操作。合并后的完整代碼:
/** * 連接數(shù)據(jù)庫(kù)并根據(jù)輸入的SQL語(yǔ)句查詢數(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ù)庫(kù)———–“); 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ù)列名稱查詢出該列對(duì)應(yīng)的數(shù)據(jù) String rs = resultSet.getString(index); //將其放入列和值存放HashMap中 hashMap.put(index,rs); //將多個(gè)HashMap合并成一個(gè)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ù)庫(kù) stmt.close(); conn.close(); System.out.println(“查詢連接結(jié)束”); } catch (ClassNotFoundException e) { e.printStackTrace(); }catch (SQLException e){ e.printStackTrace(); } return res; } /** * 將SQL語(yǔ)句進(jìn)行拆分 * @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組件進(jìn)行編寫SQL語(yǔ)句
項(xiàng)目總結(jié)
在使用 JDBC 來進(jìn)行數(shù)據(jù)庫(kù)的操作,在使用時(shí)由于對(duì)其方法并不是全部了解,所以需要邊看源碼邊改善功能,因此遇到問題時(shí)才會(huì)感覺到腦中知識(shí)儲(chǔ)備的不足,目前只是實(shí)現(xiàn)了查詢操作,還未完善 CRUD 全部過程的操作。