跳到主要内容

在MySQL中使用PreparedStatement

· 阅读需 5 分钟

背景

有个只会SQL的校友在群里问了一个MySQL表达式求值有关的问题,大意是数据库里存储的是表达式,但需要计算表达式的结果进行查询。刚开始我以为MySQL里有类似eval 的函数,后来看了一下并没有,与之类似的是PreparedStatement ,然后结合存储过程,实现了在MySQL中进行表达式求值。这里,我将问题简化下,然后以此为例,介绍MySQL中的PreparedStatement

问题

建表语句:

create table expression_test
(
id bigint auto_increment primary key,
expression varchar(255) default '' not null comment '表达式',
result varchar(64) default '' not null comment '结果'
);

在这个表里,expression 列是一些表达式(算术运算),result 列是计算结果。初始状态下,表里expression 列有一些表达式,result 列为空,现需要计算表达式的值,并将结果填到result 列里。

如果不限制只能使用SQL,那这个问题并不算很困难,可以将表达式查出来,然后计算并写入结果即可,不过其实也可以用PreparedStatement。但如果限制只能使用SQL就有一定难度了,毕竟很少有这样的使用场景。

使用JDBC

由于现在一般使用Mybatis等ORM框架,所以基本不写JDBC代码了。但是,JDBC可以使用PreparedStatement ,Mybatis等ORM框架也是调用了JDBC的这些功能。

实现代码如下,主要关注第7行:

try (Connection conn = DriverManager.getConnection(url, user, passwd)) {
Statement statement = conn.createStatement();
ResultSet result = statement.executeQuery("select id, expression from expression_test");
while (result.next()) {
long id = result.getLong("id");
String expression = result.getString("expression");
String sql = "update expression_test set result = " + expression + " where id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setLong(1, id);
stmt.executeUpdate();
stmt.close();
}
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}

为了对表达式进行求值,这里采用字符串拼接的方式,存在SQL注入问题。对于id,使用? 占位符,这种方式可以避免SQL注入。

假设expression3*5+2id1 ,那么SQL将变成

update expression_test set result = 3*5+2 where id = 1;

从而实现了将表达式的值插入result 的功能。当然,为了避免SQL注入,可以使用表达式引擎进行表达式求值,不过这不是本文的重点。

使用SQL

可以直接在SQL中使用PreparedStatement ,配合存储过程,也能实现这样的效果。

PreparedStatement 的SQL语法基于3个命令:

  • PREPARE :准备一个用于执行的语句。也就是一个SQL,SQL里可以用? 作为占位符
  • EXECUTE :执行准备的语句。可以使用USING 来替换占位符
  • DEALLOCATE PREPARE :释放准备语句的资源

先看一个简单的例子:

set @id = 1;
select @expression := expression from expression_test where id = @id;
set @sql = concat('update expression_test set result = ', @expression, ' where id = ?');
prepare stmt from @sql;
execute stmt using @id;
deallocate prepare stmt;

先指定id,然后查出表达式。在第3行,使用字符串拼接将表达式直接拼接到SQL中,从而实现了表达式求值,对于id ,使用占位符。

在4~6行,通过prepareexecutedeallocate prepare 使用PreparedStatement ,执行构造的SQL语句,从而实现对表达式值的插入。

有了这个作为基础,结合存储过程,即可通过循环处理表中的每一行。

delimiter //
create procedure test()
begin
declare i bigint;
declare done bool default false;
declare cur cursor for select id from expression_test;
declare continue handler for not found set done = true;
open cur;
label: loop
fetch cur into i;
if done then
leave label;
end if;
select expression from expression_test where id = i into @expression;
set @sql = concat('update expression_test set result = ', @expression, ' where id = ', i);
prepare stmt from @sql;
execute stmt;
deallocate prepare stmt;
end loop;
close cur;
end //
delimiter ;
call test();

参考文档