SpringBoot +JdbcTemplate+VUE 实现在线输入SQL语句返回数据库结果

news2024/10/7 8:24:57

文章目录

    • 前言
    • 框架选型
    • SpringBoot 结合JdbcTemplate简单示例
    • SpringBoot 结合JdbcTemplate实现运行SQL
      • 处理思路
      • 后端处理
      • 前端处理

在这里插入图片描述

前言

想起来要做这个功能是因为我们公司的预生产环境和生产环境如果想要连接数据库都需要登录堡垒机,然后再通过堡垒机进行跳转到对应定制的Navicat 连接工具进行查询。每次这个过程十分繁琐,所以就想直接在我们的系统上直接做个口子,登录以后,可以直接输入SQL查询,跟直接连接Navitcat效果是一样的

image-20231022175350150

我本地用的是DataGrip 直接运行结果如下:

image-20231022175441738

框架选型

那么想要实现一个这个功能需要准备什么呢?

首先我这里后端是采用SpringBoot 结合JdbcTemplate ,而对于前端展示的话,是采用了VUE+Element UI

Maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
 <dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

SpringBoot 结合JdbcTemplate简单示例

这里我们主要实现接口是通过JdbcTemplate的query(String sql, RowMapper rowMapper)方法,

该方法用于执行提供的SQL查询,并将查询结果通过RowMapper接口进行映射。

下面对该方法的参数和用法进行一些解析:

  • String sql:要执行的SQL查询语句。
  • RowMapper<T> rowMapper:用于将结果集的每一行映射到具体对象的映射器接口。它定义了一个mapRow(ResultSet rs, int rowNum)方法,用来映射结果集中的每一行数据。rs参数代表结果集,rowNum参数表示当前行号。该方法将会被JdbcTemplate在每一行数据上调用。

下面展示了一个示例,使用query(String sql, RowMapper<T> rowMapper)方法查询数据库,将结果映射到Java对象中:

public class UserRepository {
    private JdbcTemplate jdbcTemplate;
    
    public List<User> getAllUsers() {
        String sql = "SELECT * FROM users";
        
        List<User> userList = jdbcTemplate.query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));

                return user;
            }
        });
        
        return userList;
    }
}

在上面的示例中,我们使用了一个匿名内部类作为RowMapper,并实现了mapRow(ResultSet rs, int rowNum)方法来完成结果集到User对象的映射,其中rs参数用于访问结果集的列,rowNum参数用来表示当前行的索引,我们根据列名获取对应字段的值并设置到User对象中。

当我们调用语句jdbcTemplate.query(sql, rowMapper)时,JdbcTemplate会执行查询并将结果通过rowMapper进行映射,最终返回一个包含映射后的对象列表的List。

上述示例仅展示了基本用法,我们可以根据实际需要进行进一步的调整和自定义映射逻辑。

SpringBoot 结合JdbcTemplate实现运行SQL

如果对于上面的例子理解了,对于实现我们的功能就变得异常简单了。

处理思路

  • SQL格式化: 我们前端传入的是一个SQL字符串,我们这里需要将对应的SQL先进行处理,去掉多余的一些空格,防止运行报错,
  • 执行前先count一下总数进行限制(这个步骤程序里面的处理有点问题,先注释去掉了,只是提供了一个思路): 我们传入的SQL语句运行结果可能会有几万几十万甚至更大的数据,如果select了一张大表又不加以限制,那可能导致程序直接OOM,所以我们这里在运行SQL前,先count一下对应的sql数量如果不超过某个总数才可以直接进行查询。
  • 处理查询结果 :由于我们的在线查询每次返回的都是不一样的结果,所以进行接收肯定是用map,而不能采用我们平常使用的实体entity接收
  • 前端动态渲染列: 另外对于我们每次查询的sql,对应的列是不一样的,所以我们的前端vue需要根据返回的结果进行动态渲染列

后端处理

先看一下Postman返回结果展示: RunSqlVO主要就是两块内容,一个就是查询结果,一个就是对应的列名集合(给前端进行动态渲染表格使用)

@Data
public class RunSqlVO {
	//数据库返回的map数据list
	private List<Map<String, Object>> queryResult;
	//对应的列名集合
	private List <Map<String, Object>>columns;

}

image-20231022183513609

先给出核心代码:主要就是上面说的4步骤:

	@PostMapping("/queryData")
	public Object queryData(@RequestBody RunSqlParam runSqlParam) {
		JsonResponse jsonResponse = ResponseFactory.newJsonResponse(SystemCode.SUCCESS, "执行成功");
		//1、格式化sql
		String sql = formatSqlToOneLine(runSqlParam.getSql());
		//2、执行前先count一下总数进行限制
		// 这块处理有点问题,可以先去掉: 使用正则表达式匹配 SELECT 语句中的字段部分
	/*	String countSql = getCountSql(sql);
		Integer count = jdbcTemplate.queryForObject(countSql, Integer.class);
		if (count > MAX_COUNT) {
			return jsonResponse;
		}*/
		3、处理查询结果
		List<Map<String, Object>> queryResult = jdbcTemplate.query(sql
				.replaceAll("[\\s]+", " "), (rs, rowNum) -> handleData(rs));
		RunSqlVO runSqlVO = new RunSqlVO();
		//4、根据queryResult数据库列名,进行前端动态渲染列
		handleColumn(queryResult, runSqlVO);
		runSqlVO.setQueryResult(queryResult);
		return jsonResponse.setData(runSqlVO);

	}
	//处理数据库返回结果数据
	private static Map<String, Object> handleData(ResultSet rs) throws SQLException {
		Map<String, Object> row = new LinkedHashMap<>();
		ResultSetMetaData metaData = rs.getMetaData();
		int columnCount = metaData.getColumnCount();
		for (int i = 1; i <= columnCount; i++) {
			String columnName = metaData.getColumnName(i);
			Object value = rs.getObject(i);
			//如果是字符串太长了,只展示前200个
			if (Objects.nonNull(value)) {
				value = value.toString().length() > MAX_SHOW_CONTENT ?
						value.toString().substring(0, MAX_SHOW_CONTENT) : value;
			}
			//针对日期进行格式化
			if (value instanceof Date){
				Date date = (Date) value;
				value=DateUtil.formatDate(date);
			}

			row.put(columnName, value);
		}
		return row;
	}
	//根据queryResult 结果列里面返回对应前端的列名进行渲染
	private static void handleColumn( List<Map<String, Object>> queryResult,
								  RunSqlVO runSqlVO) {
		List<Map<String, Object>> columnList=new ArrayList<>();
		Map<String, Object> map = queryResult.get(0);
		for (String key : map.keySet()) {
			Map<String, Object> column = Maps.newHashMap();
			column.put("field", key);
			column.put("label", key);
			columnList.add(column);
		}
		runSqlVO.setColumns(columnList);
	}

	// 使用正则表达式匹配 SELECT 语句中的字段部分,并生成对应的 COUNT 语句
	private static String getCountSql(String selectSql) {
		// 定义正则表达式匹配模式
		String regex = "^SELECT (.*) FROM";
		String replacement = "SELECT COUNT(*) FROM";

		// 使用正则表达式进行匹配和替换
		Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(selectSql);
		String countSql = matcher.replaceFirst(replacement);

		return countSql;
	}
	//格式化SQL
	public static String formatSqlToOneLine(String sql) {
		// 去除 SQL 语句中的换行符和多余的空格
		sql = sql.replaceAll("\\s+", " ");
		sql = sql.replaceAll("\\r?\\n", "");
		return sql;
	}

1、格式化sql

	public static String formatSqlToOneLine(String sql) {
		// 去除 SQL 语句中的换行符和多余的空格
		sql = sql.replaceAll("\\s+", " ");
		sql = sql.replaceAll("\\r?\\n", "");
		return sql;
	}

2、执行前先count一下总数进行限制

String countSql = getCountSql(sql);
Integer count = jdbcTemplate.queryForObject(countSql, Integer.class);
if (count > MAX_COUNT) {
    return jsonResponse;
}

// 使用正则表达式匹配 SELECT 语句中的字段部分,并生成对应的 COUNT 语句
private static String getCountSql(String selectSql) {
   // 定义正则表达式匹配模式
   String regex = "^SELECT (.*) FROM";
   String replacement = "SELECT COUNT(*) FROM";

   // 使用正则表达式进行匹配和替换
   Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
   Matcher matcher = pattern.matcher(selectSql);
   String countSql = matcher.replaceFirst(replacement);

   return countSql;
}

3、处理查询结果

由于我们的在线查询每次返回的都是不一样的结果,所以进行接受肯定是用map,而不能采用我们平常使用的实体entity接收

	private static Map<String, Object> handleData(ResultSet rs) throws SQLException {
		Map<String, Object> row = new LinkedHashMap<>();
		ResultSetMetaData metaData = rs.getMetaData();
		int columnCount = metaData.getColumnCount();
		for (int i = 1; i <= columnCount; i++) {
			String columnName = metaData.getColumnName(i);
			Object value = rs.getObject(i);
			//如果是字符串太长了,只展示前200个
			if (Objects.nonNull(value)) {
				value = value.toString().length() > MAX_SHOW_CONTENT ?
						value.toString().substring(0, MAX_SHOW_CONTENT) : value;
			}
			//针对日期进行格式化
			if (value instanceof Date){
				Date date = (Date) value;
				value=DateUtil.formatDate(date);
			}

			row.put(columnName, value);
		}
		return row;
	}

4、queryResult里面包含了列名,根据这个返回对应的列名即可,进行前端动态渲染列

这里返回列字段是field和label主要是为了和前端处理对应上:

fileld,对应的英文名,label对应的实际展示名,我们这里前端就直接展示数据库对应的列了,当然也可以展示成对应数据库的comment注释。

	private static void handleColumn( List<Map<String, Object>> queryResult,
								  RunSqlVO runSqlVO) {
		List<Map<String, Object>> columnList=new ArrayList<>();
		//默认取第一个查询结果即可,里面会有对应的列名
		Map<String, Object> map = queryResult.get(0);
		for (String key : map.keySet()) {
			Map<String, Object> column = Maps.newHashMap();
			column.put("field", key);
			column.put("label", key);
			columnList.add(column);
		}
		runSqlVO.setColumns(columnList);
	}

拓展:如果想要实现展示列名是对应数据库里面的中文comment,可以参考这个方法可以返回对应的列名,这里暂未实现。

这里主要是采用了SHOW FULL COLUMNS FROM tableName语句

	public List<Map<String, Object>> getColumnComments(String tableName) {
		// 执行查询语句获取列注释
		String query = "SHOW FULL COLUMNS FROM " + tableName;
		return jdbcTemplate.query(query, (rs, rowNum) -> {
			Map<String, Object> row = new HashMap<>();
			String columnName = rs.getString("Field");
			String columnComment = rs.getString("Comment");

			row.put("columnName", columnName);
			row.put("columnComment", columnComment);

			return row;
		});

	}

类似这样的结果:

image-20231022184052568

前端处理

前端我们这里有个需要特别处理的点就是需要根据后端每次返回的不同的列进行渲染不一样的表格

比如我这里select demo_id,demo_code,demo_name,status_desc,start_date5个字段,就展示5个列的表格,3个列就只展示3个了

image-20231022184305912

3个列展示对应的表格

image-20231022184419378

完整的VUE页面代码:

<template>
  <div class="grid-table-container">
    <el-card class="box-card">

      <el-input
        type="textarea"
        :autosize="{ minRows:5, maxRows: 10}"
        placeholder="请输入内容"
        v-model="sql"
      >
      </el-input>
      <div style="margin-top: 20px;border-top: 1px solid #E8E8E8;">
        <el-form :inline="true" :model="formInline" class="demo-form-inline"
                 style="margin-left: 10px; margin-top: 20px">

          <el-form-item>
            <el-button type="primary" @click="onSubmit" style="margin-left: 20px">查询</el-button>
          </el-form-item>
          <el-form-item>
            <el-button type="info" plain @click="onReset">重置</el-button>
          </el-form-item>
        </el-form>
      </div>

      <el-table :data="tableData" style="width: 100%" border>
        <el-table-column align="center" v-for="column in columns" :key="column.field" :prop="column.field"
                         :label="column.label"
        ></el-table-column>
      </el-table>

    </el-card>

  </div>
</template>

<script>
import { runSql } from '@/api/sys/user'
export default {
  methods: {
    onSubmit() {
      runSql({ 'sql': this.sql }).then(res => {
        console.log(res)
        this.columns = res.data.columns
        this.tableData = res.data.queryResult
      })
    },

    fetchColumnsFromBackend() {
      // 发送请求到后端获取字段信息
      // 假设后端返回的字段信息格式为:
      // [
      //   { field: 'name', label: '姓名' },
      //   { field: 'age', label: '年龄' },
      //   ...
      // ]
      // 将后端返回的字段信息赋值给 this.columns
      this.columns = [
        { field: 'name', label: '姓名' },
        { field: 'age', label: '年龄' }
        // ...
      ]

      // 获取表格数据
      this.fetchTableDataFromBackend()
    },
    fetchTableDataFromBackend() {
      // 发送请求到后端获取表格数据
      // 假设后端返回的数据格式为:
      // [
      //   { name: '张三', age: 20 },
      //   { name: '李四', age: 25 },
      //   ...
      // ]
      // 将后端返回的数据赋值给 this.tableData
      this.tableData = [
        { name: '张三', age: 20 },
        { name: '李四', age: 25 }
        // ...
      ]
    }

  },
  mounted() {
    // 从后端获取字段信息,并根据字段信息生成动态列
    // this.fetchColumnsFromBackend();
  },
  data() {
    return {
      sql: 'select * from sys_role',
      tableData: [], // 从后端获取的数据
      columns: [] // 动态列数组
    }
  }
}
</script>

核心就是里面的:根据列循环遍历渲染

<el-table :data="tableData" style="width: 100%" border>
<el-table-column align="center" v-for="column in columns" :key="column.field" :prop="column.field"
:label="column.label">
</el-table-column>

而这里对应后端返回的结果也就是前面提到的这个方法进行处理的

	private static void handleColumn( List<Map<String, Object>> queryResult,
								  RunSqlVO runSqlVO) {
		List<Map<String, Object>> columnList=new ArrayList<>();
		Map<String, Object> map = queryResult.get(0);
		for (String key : map.keySet()) {
			Map<String, Object> column = Maps.newHashMap();
			column.put("field", key);
			column.put("label", key);
			columnList.add(column);
		}
		runSqlVO.setColumns(columnList);
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1024 蓝屏漏洞攻防战(第十九课)

1024 蓝屏漏洞攻防战(第十九课) 思维导图 一 永恒之蓝的介绍 漏洞为外界所知源于勒索病毒的爆发,该病毒利用NSA(美国国家安全局)泄露的网络攻击工具 永恒之蓝( EternalBlue )改造而成,漏洞通过TCP的445和139端口,利用SMB远程代码执行漏洞,攻击者可以在目标系统上执行…

详解对于ReadView 机制如何判断当前事务能够看见

ReadView 机制就是用来判断当前事务能够看见哪些版本的&#xff0c;一个 ReadView 主要包含如下几个部分&#xff1a; m_ids&#xff1a;生成 ReadView 时有哪些事务在执行但是还没提交的&#xff08;称为 “活跃事务”&#xff09;&#xff0c;这些活跃事务的 id 就存在这个字…

for、while、do While、for in、forEach、map、reduce、every、some、filter的使用

for、while、do While、for in、forEach、map、reduce、every、some、filter的使用 for let arr [2, 4, 6, 56, 7, 88];//for for (let i 0; i < arr.length; i) {console.log(i : arr[i]) //0:2 1:4 2:6 3:56 4:7 5:88 }普通的for循环可以用数组的索引来访问或者修改…

AI篇-如何用AI辅助对图片进行鉴赏

前言 目录 前言 一、观众侧鉴赏图片 方法1&#xff1a;直接将图片发给文心一言&#xff0c;让文心一言分析。 方法2&#xff08;正确方法&#xff09;&#xff1a;将图片简单介绍并把图片发给文心一言&#xff0c;让文心一言分析。 二、作者介绍图片 方法&#xff08;正…

智慧公厕管理系统:让公厕更智能、更高效的利器

公厕是城市基础设施的重要组成部分&#xff0c;然而&#xff0c;由于管理不善和公共卫生意识的薄弱&#xff0c;公厕经常面临着脏乱差的问题&#xff0c;令人不愿意使用。为了改善公厕管理的现状&#xff0c;智慧公厕管理系统的出现成为了一种创新的解决方案。 智慧公厕管理系…

大模型微调发展-学习调研总结

模型微调前言 https://blog.csdn.net/weixin_39663060/article/details/130724730 针对于小公司&#xff0c;如何能够利用开源的大模型&#xff0c;在自己的数据上继续训练&#xff0c;从而应用于自己的业务场景&#xff1f;或低成本的方法微调大模型。 目前主流的方法包括201…

小程序之微信登录授权(6)

⭐⭐ 小程序专栏&#xff1a;小程序开发专栏 ⭐⭐ 个人主页&#xff1a;个人主页 目录 一.了解微信授权登录 小程序登录授权基本原理&#xff1a; 二.微信授权登录演示 三.微信授权与后端的交互 3.1后台代码&#xff1a; 3.2 前端代码&#xff1a; 四.微信退出 五.微信表情包…

DELM深度极限学习机回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

自然语言处理---Self Attention自注意力机制

Self-attention介绍 Self-attention是一种特殊的attention&#xff0c;是应用在transformer中最重要的结构之一。attention机制&#xff0c;它能够帮助找到子序列和全局的attention的关系&#xff0c;也就是找到权重值wi。Self-attention相对于attention的变化&#xff0c;其实…

项目总结-商品购买流程

&#xff08;1&#xff09;添加购物车 Controller&#xff1a; CartService&#xff1a; 实现类&#xff1a; CartDetail detaildao.queryByCdid(cid,gds.getId()); CartDao&#xff1a; //获取详情对象Select("select * from t_cartdetail where cid#{cid} and gid#{gid…

buu第五页 wp

[RootersCTF2019]babyWeb 预期解 一眼就是sql注入&#xff0c;发现过滤了 UNION SLEEP " OR - BENCHMARK盲注没法用了&#xff0c;因为union被过滤&#xff0c;堆叠注入也不考虑&#xff0c;发现报错有回显&#xff0c;尝试报错注入。 尝试&#xff1a; 1||(updatex…

ubuntu20.04下安装nc

前言 nc在网络渗透测试中非常好用&#xff0c;这里的主要记一下Ubuntu20.04中nc的安装 编译安装 第一种方式是自己编译安装&#xff0c;先下载安装包 nc.zip wget http://sourceforge.net/projects/netcat/files/netcat/0.7.1/netcat-0.7.1.tar.gz/download -O netcat-0.7.…

anyproxy 的安装和抓包使用

简介 AnyProxy是阿里开发的开源的代理服务器&#xff0c;主要特性包括&#xff1a; 基于Node.js&#xff0c;开放二次开发能力&#xff0c;允许自定义请求处理逻辑支持Https的解析提供GUI界面&#xff0c;用以观察请求 安装运行Anyproxy 首先需要电脑由安装 node&#xff0…

H3C SecParh堡垒机 data_provider.php 远程命令执行漏洞

构造poc执行远程命令&#xff1a; /audit/data_provider.php?ds_y2019&ds_m04&ds_d02&ds_hour09&ds_min40&server_cond&service$(id)&identity_cond&query_typeall&formatjson&browsetrue漏洞证明&#xff1a; 文笔生疏&#xff0c…

【大模型应用开发教程】02_LangChain介绍

LangChain介绍 什么是 LangChain1. 模型输入/输出2. 数据连接3. 链&#xff08;Chain&#xff09;4. 记忆&#xff08;Meomory&#xff09;5. 代理&#xff08;Agents&#xff09;6.回调&#xff08;Callback&#xff09;在哪里传入回调 ?你想在什么时候使用这些东西呢&#x…

1024常玩到的漏洞(第十六课)

1024常玩到的两个漏洞(第十六课) 漏洞扫描工具 1024渗透OpenVas扫描工具使用(第十四课)-CSDN博客 流程 一 ms12-020漏洞分析 MS12-020漏洞是一种远程桌面协议(RDP)漏洞。在攻击者利用该漏洞之前,它需要将攻击者的计算机连接到受害者的计算机上。攻击者可以通过向受害者计算…

跟着NatureMetabolism学作图:R语言ggplot2转录组差异表达火山图

论文 Independent phenotypic plasticity axes define distinct obesity sub-types https://www.nature.com/articles/s42255-022-00629-2#Sec15 s42255-022-00629-2.pdf 论文中没有公开代码&#xff0c;但是所有作图数据都公开了&#xff0c;我们可以试着用论文中提供的数据…

Linux进程与线程的内核实现

进程描述符task_struct 进程描述符&#xff08;struct task_struct&#xff09;pid与tgid进程id编号分配规则内存管理mm_struct进程与文件,文件系统 进程,线程创建的本质 clone函数原型线程创建的实现进程创建的实现 总结 进程描述符task_struct 进程描述符&#xff08;st…

自动驾驶的商业应用和市场前景

自动驾驶技术已经成为了交通运输领域的一项重要创新。它不仅在改善交通安全性和效率方面具有巨大潜力&#xff0c;还为各种商业应用提供了新的机会。本文将探讨自动驾驶在交通运输中的潜力&#xff0c;自动驾驶汽车的制造商和技术公司&#xff0c;以及自动驾驶的商业模式和市场…

Git GUI工具:SourceTree代码管理

Git GUI工具&#xff1a;SourceTree SourceTreeSourceTree的安装SourceTree的使用 总结 SourceTree 当我们对Git的提交、分支已经非常熟悉&#xff0c;可以熟练使用命令操作Git后&#xff0c;再使用GUI工具&#xff0c;就可以更高效。 Git有很多图形界面工具&#xff0c;这里…