如何将Oracle的JSON_VALUE函数与PreparedStatement一起使用

时间:2019-07-09 08:05:04

标签: json oracle jdbc prepared-statement

我正在尝试使用json_value()使用Oracle的PreparedStatement函数运行SQL查询。

假设设置下表:

drop table foo cascade constraints purge;
create table foo
(
  id integer primary key, 
  payload clob, 
  constraint ensure_json check (payload IS JSON STRICT)
);

insert into foo values (1, '{"data": {"k1": 1, "k2": "foo"}}');

以下SQL查询工作正常:

select *
from foo
where json_value(payload, '$.data.k1') = '1'

并返回预期的行。

但是,当我尝试使用PreparedStatement来运行此查询时,如以下代码所示:

String sql =
     "select *\n" +
     "from foo\n" +
     "where json_value(payload, ?) = ?";

PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "$.data.k1");
pstmt.setString(2, "1");
ResultSet rs = pstmt.executeQuery();

(为简化起见,我从示例中删除了所有错误检查)

结果是:

  

java.sql.SQLException:ORA-40454:路径表达式不是文字

罪魁祸首正在传递json路径值(参数索引1),第二个参数没问题。

当我(仅)用字符串常量json_value(payload, '$.data.k1') = ?替换第一个参数时,prepared语句可以正常工作。

我不顾一切地尝试,还尝试在参数pstmt.setString(1, "'$.data.k1'")中包括单引号,但是毫不奇怪,Oracle也不会接受它(相同的错误消息)。

我还尝试使用json_value(payload, concat('$.', ?) ),并且仅传递"data.k1"作为参数-结果相同。

所以,问题是:

  • 如何使用json_value参数将JSON路径表达式传递给Oracle的PreparedStatement函数?

有什么想法吗?这是驱动程序还是Oracle中的错误? (我在My Oracle Support上找不到任何内容)

还是这仅仅是“未实现”的情况?


环境:

我正在使用Oracle 18.0
我尝试了ojdbc10.jar驱动程序的18.3和19.3版本以及OpenJDK11。

1 个答案:

答案 0 :(得分:2)

它不是驱动程序-您得到的是with dynamic SQL

declare
  result foo%rowtype;
begin
  execute immediate 'select *
    from foo
    where json_value(payload, :1) = :2'
  into result using '$.data.k1', '1';
  dbms_output.put_line(result.payload);
end;
/

ORA-40454: path expression not a literal
ORA-06512: at line 4

它并不是真正的错误,它是documented(添加了重点):

  

JSON_basic_path_expression

     

使用此子句来指定SQL / JSON路径表达式。该函数使用路径表达式来评估 expr 并找到与路径表达式匹配或满足的标量JSON值。 路径表达式必须是文本文字。有关 JSON_basic_path_expression 的完整语义,请参见Oracle Database JSON Developer's Guide

不幸的是,您将不得不embed the path literal,而不是绑定它:

declare
  result foo%rowtype;
begin
  execute immediate 'select *
    from foo
    where json_value(payload, ''' || '$.data.k1' || ''') = :1'
  into result using '1';
  dbms_output.put_line(result.payload);
end;
/

1 rows affected

dbms_output:
{"data": {"k1": 1, "k2": "foo"}}

或对于您的JDBC示例(将路径保留为单独的字符串,因为您大概希望它实际上是一个变量):

String sql =
     "select *\n" +
     "from foo\n" +
     "where json_value(payload, '" + "$.data.k1" + "') = ?";

PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "1");
ResultSet rs = pstmt.executeQuery();

显然不是您想要要做的事*,但似乎没有其他选择。除了将查询转换为函数并将路径变量传递给函数外,该函数随后必须使用动态SQL,因此效果大体相同-也许更容易处理SQL注入问题。

*,我知道您知道如何以嵌入式方式执行此操作,并且您知道要使用绑定变量,因为这是正确的操作;我已经说明了其他用户* em-以外的其他需求* 8-)