将insert语句转化为select语句,进行校验,验证是否插入成功

前言

  公司的测试和运维不太懂sql,每次将sql文件交给他们到测试或者生产数据库中执行sql的时候,他们都是全部执行,不看是否执行成功。因此,就会导致有些insert语句没有执行成功,等到功能验证的时候就会出问题,到时候又得是开发一步步寻找bug,最后找到数据库中发现数据缺失……

  因此,我写了一个接口,将sql文件中的insert语句进行扫描,转换成select语句进行查询,如果查询得到就说明insert语句执行成功,查询不到说明执行失败。

注意:

  项目开发的时候,很少会对数据进行删除,所以很少会运用到delete语句。而update语句的话,我们小组规定,为了能够成功将数据存入到数据库中,会将update语句转化为insert语句,如下:

比如我想把id=1,name=张三的数据,将他的age改为19。

insert into student (id,name,age) values (1,'张三',19) on duplicate key update age = 19;

上面这条语句的意思是将(id=1,name=张三,age=19)的数据插入到student表中,如果有字段冲突(主键冲突或者唯一索引冲突),就会将原来的数据的age修改为19。

假如我数据库中已经有(1,‘张三’,18)的数据,当我执行上面的sql时,由于id主键的冲突,就会无法进行数据的插入,反而是将原来的数据的age=18改为19。

思路

总共分六步:

1、扫描文件,获得insert语句;

2、扫描insert语句,获取表名和字段;

3、扫描insert语句,获取字段值;

4、根据2、3步获取的表名、字段和字段值,组合成一个map;

5、根据上一步得到的map,拼接key和value,得到select语句,并执行;

6、若查询成功,则insert语句执行成功;若查询失败,则说明insert语句执行失败。
  

此外,由于我们要自己拼接select语句,就不能像使用mybatis一样将sql写在xml文件中,这样子无法动态的拼接sql。因此,我们使用spring的JdbcTemple来查询数据库。

在yml写入数据库连接信息。

spring:
  application:
    name: test-module
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/production_center?serverTimezone=UTC
    username: root
    password: 123456

代码中直接注入JdbcTemplate。

@Autowired
private JdbcTemplate jdbcTemplate;

代码

从文件中扫描得到insert的sql语句

    /**
     * 从文件中扫描得到insert的sql语句
     * @return
     */
    public List<String> getSql(MultipartFile file) {
        try {
            InputStream stream = file.getInputStream();
            // 字节流转换成字符流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
            StringBuilder str = new StringBuilder();
            String line = null;
            List<String> stringList = new ArrayList<>();
            //按行读取
            while ((line = bufferedReader.readLine()) != null) {
                // 获取insert语句
                if (line.startsWith("INSERT") || line.startsWith("insert")) {
                    while (line != null) {
                        if (line.endsWith(";")) {
                            str.append(line);
                            stringList.add(str.toString());
                            str.delete(0, str.length());
                            break;
                        }
                        if (line == null) {
                            continue;
                        }
                        str.append(line);
                        line = bufferedReader.readLine();
                    }
                }
            }
            bufferedReader.close();

            return stringList;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

获取insert语句中的key值

	/**
     * 获取insert语句中的key值
     * @param str
     * @return
     */
    public List<String> getKeys(String str) {
        ArrayList<String> key = new ArrayList<>();
        String regex = "`(\\w+)`(?!\\.)";
        Pattern compile = Pattern.compile(regex);
        Matcher matcher = compile.matcher(str);
        while (matcher.find()) {
            key.add(matcher.group(1).toString());  //group(1):表示第一个()里的内容
        }

        // 如果表名和字段名没有反单引号,采取下列措施
        if (CollectionUtils.isEmpty(key)) {
            Pattern compileTable = Pattern.compile("(to|TO) (\\w+)");
            Matcher matcherTable = compileTable.matcher(str);
            if (matcherTable.find()) {
                key.add(matcherTable.group(2).toString());
            }
            Pattern compile1 = Pattern.compile("(\\()(.+)(\\))( ?)(V|v)");
            Matcher matcher1 = compile1.matcher(str);
            while (matcher1.find()) {
                String s = matcher1.group(2).toString();
                Arrays.stream(s.replace(" ", "").split(",")).forEach(item -> key.add(item));
            }
        }

        return key;
    }

获取insert语句中的value值

 /**
     * 获取insert语句中的value值
     * @param str
     * @return
     */
    public List<String> getValues(String str) {
        //获得字段值
        String string = "";
        if ("".equals(string)) {
            String regex2 = "(values ?|VALUES ?)(\\()(.+)(\\))";
            Pattern compile2 = Pattern.compile(regex2);
            Matcher matcher2 = compile2.matcher(str);
            while (matcher2.find()) {
                string = matcher2.group(3).toString();
            }
        }

        List<String> list = new ArrayList<>();
        log.info("str的长度={}",string.length());

        String concatStr = "";

        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);

            // 针对括号里的内容进行获取
            if(string.charAt(i) == '(') {
                int num = 0;
                for (int j = i; j < string.length(); j++) {
                    if (string.charAt(j) == '(') {
                        num++;
                    }
                    if (string.charAt(j) == ')') {
                        concatStr += string.charAt(j);
                        num --;
                        if (num == 0) {
                            i = j+1;
                            break;
                        }
                        continue;
                    }
                    concatStr += string.charAt(j);
                }
            }

            // 针对单引号里的内容进行获取
            if (c == '\'' || c == '\"') {
                if (string.charAt(i+1) == '\'' || string.charAt(i+1) == '\"') {
                    i++;
                    continue;
                }
                for (int j = i+1; j < string.length(); j++) {
                    char charAt = string.charAt(j);
                    if (charAt == '\'' || charAt == '\"') {
                        i = j+1;
                        break;
                    }
                    concatStr += charAt;
                }
            }

            if(i >= string.length()){
                break;
            }

            if (i == string.length()-1) {
                if (string.charAt(i) == '\'' || c == '\"') {
                    break;
                }
                else {
                    concatStr += c;
                    break;
                }
            }

            if (string.charAt(i) == ',') {
                list.add(concatStr);
                concatStr = "";
                continue;
            }

            if (c == ' '){
                continue;
            }


            concatStr += string.charAt(i);
        }
        list.add(concatStr);
        return list;
    }

得到一个key为字段value为参数值的map

    /**
     * 得到一个key为字段value为参数值的map
     *
     * @param sql
     * @return
     */
    public HashMap<String, Object> getInsertMap(String sql) {

         //获得字段
        List<String> key = getKeys(sql);
        log.info("key = {}", JSON.toJSONString(key));
        
        //获得字段值
        List<String> value = getValues(sql);
        log.info("value={}", JSON.toJSONString(value));


        //组合成一个map
        HashMap<String, Object> map = new HashMap<>();
        try {
            for (int i = 0; i < value.size(); i++) {
                if (i == 0) {
                    // 将表名放入map中
                    map.put("table", key.get(i));
                    continue;
                }
                map.put(key.get(i + 1), value.get(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 去除公式生成的time或其它随机生成的id等信息
        map.remove("param_id");
        map.remove("create_time");
        map.remove("modify_time");
        ArrayList<String> delete = new ArrayList<>();
        // 去除字段值为NULL和null还有为空的字段
        for (String strKey : map.keySet()) {
            if (map.get(strKey).equals("NULL") || map.get(strKey).equals("") || map.get(strKey).equals("null")) {
                delete.add(strKey);
            }
        }
        //删除值为空或为Null或为随机数的字段和值
        delete.stream().forEach(item -> map.remove(item));

        log.info("map={}", JSON.toJSONString(map));
        return map;
    }

拼接成select语句

 /**
     * 拼接sql语句
     *
     * @param map
     * @return
     */
    public Map<String, Object> splitSql(HashMap<String, Object> map) {
        String table = (String) map.get("table");
        map.remove("table");
        StringBuilder builder = new StringBuilder();
        for (String key : map.keySet()) {
            builder.append(key + " = " + "'" + map.get(key) + "'" + " and ");
        }
        // 去除最后的”and“
        builder.delete(builder.length() - 4, builder.length());
        String sql = "select * from " + table + " where " + builder;
        log.info("拼接的sql为 = {}", sql);

        try {
            // 执行sql
            Map<String, Object> queryForMap = jdbcTemplate.queryForMap(sql);
            log.info("通过jdbcTemplate得到map={}", JSON.toJSONString(queryForMap));
            return queryForMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

在接口中进行校验

    /**
     * 扫描文件,将insert语句转化为select语句,查询数据库进行验证
     * @param sqlFile
     * @return
     */
    @RequestMapping("/transformToSelect")
    public List<String> transformToSelect(@RequestPart("sqlFile") MultipartFile sqlFile) {
        List<String> list = new ArrayList<>();
        HashMap<Object, Object> map = new HashMap<>();
        //从文件中获取insert语句
        List<String> sqls = upLoadService.getSql(sqlFile);

        for (int i = 0; i < sqls.size(); i++) {
            // 获得字段和值的map
            HashMap<String, Object> insertMap = upLoadService.getInsertMap(sqls.get(i));
            // 得到拼接好的select语句并查询
            Map<String, Object> sqlMap = upLoadService.splitSql(insertMap);
            if (MapUtil.isEmpty(sqlMap)) {
                log.info("出现问题的sql={}",sqls.get(i));
                list.add(sqls.get(i));
                continue;
            }
        }
        return list;
    }

改进

​ 以上,我们使用的是spring的JdbcTemplate进行数据库的查询,虽然能够动态的查询数据库,但假如我一份sql文件是查询数据库A,但另一份sql文件查询的是数据库B,这样的话我换份文件查询,还得到yml中再修改一下数据库的连接信息,然后重新构建项目,比较麻烦。

​ 因此,如果能够动态的连接不同的数据库,在导入不同sql文件之前,我只需要填写不同的数据库连接信息,就能切换到相应的数据库连接了。

解决方法

@RestController
@Slf4j
public class CzfDataSource {

    public JdbcTemplate jdbcTemplate;

    @RequestMapping("/jdbcTemplate")
    public void connectDataSource(String url, String driverClassName, String userName, String password) {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        jdbcTemplate = new JdbcTemplate(dataSource);
    }
}

调用这个接口,我们就能将项目中使用的jdbcTemplate转成我们填写的那个数据库连接了。

当然,代码中也要相应的进行更改。之前导入的jdbcTemplate要换成我们重新编写的jdbcTemplate。

//    @Autowired
//	private JdbcTemplate jdbcTemplate;
	@Autowired
    private CzfDataSource dataSource;

然后代码中使用到的jdbcTemplate改成如下:

//Map<String, Object> queryForMap = jdbcTemplate.queryForMap(sql);
Map<String, Object> queryForMap = dataSource.jdbcTemplate.queryForMap(sql);

源码

https://gitee.com/fan14/insert-select.git

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值