在 SpringBoot 中实现通过系统直接输入 SQL 查询的功能

时间:2024-10-27 06:03:39

在 SpringBoot 中实现通过系统直接输入 SQL 查询的功能

在众多企业的工作环境中,复杂的操作流程常常阻碍了员工的高效工作。以我们公司为例,在预生产和生产环境连接数据库时,必须先登录堡垒机,然后再通过工具进行查询,这一过程步骤繁琐,确实是个让人头疼的问题。

改善查询的初衷

因为现状实在太过不便,所以大家希望改进这一流程。每次查询数据库,员工们得在堡垒机间跳转,这一系列操作占用了不少时间。在效率至上的今天,这样的时间浪费实在令人惋惜。这不仅降低了员工的工作效率,还可能拖慢整个项目的进度。一旦工作时间被这些繁琐的操作占据,真正用于数据处理和分析的时间就会相应减少。在项目开发过程中,每一分钟都可能关系到最终交付成果的质量。


    org.springframework.boot
    spring-boot-starter-jdbc

 
	mysql
	mysql-connector-java

后端采用的技术

在后端技术方面,对于整个查询系统的构建而言,其作用至关重要。在此案例中,所采用的后端技术组合,每一项都承担着特定的职责。例如,某种技术专门负责与数据库的连接与交互。不同的技术组合会带来不同的性能表现和操作体验。由于各公司的业务需求各异,它们可能会选择不同的后端技术。若选择不当,可能会导致数据处理速度慢、存在安全漏洞等问题。

前端展示的关键

public class UserRepository {
    private JdbcTemplate jdbcTemplate;
    
    public List getAllUsers() {
        String sql = "SELECT * FROM users";
        
        List userList = jdbcTemplate.query(sql, new RowMapper() {
            @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;
    }
}

在展示前端内容时,VUE与UI的结合是呈现查询结果的核心方法。前端展示必须准确反映后端数据。合理运用HTML、CSS等技术对用户界面效果至关重要。一个优秀的前端界面设计,能帮助用户更直观地浏览数据,有效提升工作效率。以我公司为例,若前端界面设计混乱,员工在查阅数据时便会遇到困难,极易出现看错或遗漏关键数据的情况。

核心接口方法

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

query(sql,)这个接口是查询系统的核心,起着至关重要的作用。它负责执行SQL语句并将结果进行映射。从理论上讲,这个方法就像一座桥梁,连接着数据库查询与最终显示的结果。在使用过程中,每个参数的设定都至关重要。一旦参数设置错误,就可能引发查询结果错误或映射失败的问题。因此,员工在使用时必须格外小心,若对这种方法不熟悉,操作过程中可能会遇到不少麻烦。

返回结果展示

结果展示部分包含两大关键要素。首先是查询所得的数据,其次是列名集合。这两者对于与前端进行交互至关重要。特别是列名集合,其重要性显而易见。在一些公司的项目中,若前端未能接收到正确的列名,所显示的表格可能会出现混乱。这种情况会极大地干扰数据的解读与应用。因此,后端与前端在这一环节的交互必须确保准确无误。

	@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> 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 handleData(ResultSet rs) throws SQLException {
		Map 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> queryResult,
								  RunSqlVO runSqlVO) {
		List> columnList=new ArrayList<>();
		Map map = queryResult.get(0);
		for (String key : map.keySet()) {
			Map 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;
	}

前端特殊处理

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

前端处理的关键在于依据后端提供的列信息,对各类表格进行个性化渲染。不同的数据反馈,将直接影响到表格的具体布局。因此,在前端开发过程中,技术人员必须全面预想各种可能情形。比如,一旦数据结构发生变动,表格可能无法正常展示,这时就得迅速对代码进行调整。在实际工作中,若这一步骤出现失误,整个数据展示系统便会陷入困境。

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;
}

我想请教各位,贵公司是否曾遭遇过那些繁琐的数据库查询流程?期待大家的评论和交流。若觉得这篇文章对您有帮助,不妨点赞并分享。

	private static Map handleData(ResultSet rs) throws SQLException {
		Map 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;
	}