เตรียมสถานะในทางเลือกข้อ?


343

วิธีแก้ไขปัญหาที่ดีที่สุดสำหรับการใช้ส่วนINคำสั่งSQL กับอินสแตนซ์java.sql.PreparedStatementซึ่งไม่ได้รับการสนับสนุนสำหรับค่าหลายค่าเนื่องจากปัญหาด้านความปลอดภัยในการโจมตี SQL injection: ?ตัวยึดตำแหน่งหนึ่งแทนค่าหนึ่งค่าแทนที่จะเป็นรายการค่า

พิจารณาคำสั่ง SQL ต่อไปนี้:

SELECT my_column FROM my_table where search_column IN (?)

การใช้preparedStatement.setString( 1, "'A', 'B', 'C'" );นั้นเป็นความพยายามที่ไม่ทำงานในการแก้ปัญหาของเหตุผล?ในการใช้งานตั้งแต่แรก

มีวิธีแก้ไขปัญหาอะไรบ้าง


1
ออสการ์ฉันคิดว่าการสร้างแบบไดนามิกของ (?,?, .... ) เป็นวิธีแก้ปัญหาที่ง่ายที่สุดถ้าคุณต้องการประโยค IN แต่ฉันปล่อยให้มันเป็นการเรียกเฉพาะบุคคลเนื่องจากประสิทธิภาพเพียงพอในกรณีเฉพาะของฉัน
Chris Mazzola

6
ข้อดีอย่างหนึ่งของแถลงการณ์ที่เตรียมไว้คือ sohuld สามารถรวบรวมได้ครั้งเดียวเพื่อประสิทธิภาพ ด้วยการสร้างอนุประโยคแบบไดนามิกสิ่งนี้จะคัดค้านข้อความที่เตรียมไว้

2
จริงๆแล้วมันใช้งานได้กับ MySQL (โดยใช้ setObject เพื่อตั้งค่าอาร์เรย์ของ String เป็นค่าพารามิเตอร์) คุณใช้ฐานข้อมูลอะไร
ฟราน


นี่คือคำถามที่เกี่ยวข้อง: stackoverflow.com/q/6956025/521799
Lukas Eder

คำตอบ:


194

การวิเคราะห์ตัวเลือกต่างๆที่มีอยู่และข้อดีและข้อเสียของแต่ละสามารถใช้ได้ที่นี่

ตัวเลือกที่แนะนำคือ:

  • จัดเตรียมSELECT my_column FROM my_table WHERE search_column = ?ดำเนินการสำหรับแต่ละค่าและสหภาพผลลัพธ์ลูกค้าฝั่ง ต้องการเพียงหนึ่งข้อความที่เตรียมไว้ ช้าและเจ็บปวด
  • เตรียมSELECT my_column FROM my_table WHERE search_column IN (?,?,?)และดำเนินการ ต้องใช้หนึ่งคำสั่งที่เตรียมไว้ต่อขนาดของรายการ รวดเร็วและชัดเจน
  • เตรียมSELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...และดำเนินการ [หรือใช้UNION ALLแทนเซมิโคลอนเหล่านั้น --ed] ต้องการคำสั่งที่เตรียมไว้หนึ่งรายการต่อขนาดของรายการ ช้าลงอย่างโง่เขลาอย่างยิ่งแย่กว่าWHERE search_column IN (?,?,?)ดังนั้นฉันจึงไม่รู้ว่าทำไม Blogger ถึงแนะนำ
  • ใช้กระบวนงานที่เก็บไว้เพื่อสร้างชุดผลลัพธ์
  • เตรียมการสอบถามขนาดของรายการใน N ที่แตกต่างกัน พูดด้วยค่า 2, 10 และ 50 ในการค้นหาในรายการที่มี 6 ค่าที่แตกต่างกันเติมขนาด 10 SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)แบบสอบถามเพื่อให้ดูเหมือนว่า เซิร์ฟเวอร์ที่เหมาะสมใด ๆ จะปรับค่าที่ซ้ำกันให้เหมาะสมก่อนเรียกใช้คิวรี

ไม่มีตัวเลือกเหล่านี้ยอดเยี่ยมเลย

มีการตอบคำถามที่ซ้ำกันในสถานที่เหล่านี้ด้วยทางเลือกที่มีสติเท่าเทียมกัน แต่ก็ยังไม่มีใครที่ยอดเยี่ยมมาก:

คำตอบที่ถูกต้องหากคุณใช้ JDBC4 และเซิร์ฟเวอร์ที่รองรับx = ANY(y)จะใช้PreparedStatement.setArrayตามที่อธิบายไว้ที่นี่:

ดูเหมือนจะไม่มีวิธีใดที่จะsetArrayทำงานกับ IN-list ได้


บางครั้งคำสั่ง SQL ถูกโหลดที่รันไทม์ (เช่นจากไฟล์คุณสมบัติ) แต่ต้องการพารามิเตอร์จำนวนตัวแปร ในกรณีเช่นนี้ก่อนกำหนดแบบสอบถาม:

query=SELECT * FROM table t WHERE t.column IN (?)

จากนั้นให้โหลดคิวรี จากนั้นกำหนดจำนวนพารามิเตอร์ก่อนเรียกใช้ เมื่อทราบจำนวนพารามิเตอร์แล้วให้เรียกใช้:

sql = any( sql, count );

ตัวอย่างเช่น:

/**
 * Converts a SQL statement containing exactly one IN clause to an IN clause
 * using multiple comma-delimited parameters.
 *
 * @param sql The SQL statement string with one IN clause.
 * @param params The number of parameters the SQL statement requires.
 * @return The SQL statement with (?) replaced with multiple parameter
 * placeholders.
 */
public static String any(String sql, final int params) {
    // Create a comma-delimited list based on the number of parameters.
    final StringBuilder sb = new StringBuilder(
            new String(new char[params]).replace("\0", "?,")
    );

    // Remove trailing comma.
    sb.setLength(Math.max(sb.length() - 1, 0));

    // For more than 1 parameter, replace the single parameter with
    // multiple parameter placeholders.
    if (sb.length() > 1) {
        sql = sql.replace("(?)", "(" + sb + ")");
    }

    // Return the modified comma-delimited list of parameters.
    return sql;
}

สำหรับฐานข้อมูลบางอย่างที่ไม่สนับสนุนอาร์เรย์ผ่านข้อกำหนดคุณสมบัติ JDBC 4 วิธีนี้สามารถช่วยให้การแปลงสภาพช้า= ?ลงเป็นIN (?)เงื่อนไขข้อที่เร็วกว่าซึ่งสามารถขยายได้โดยการเรียกใช้anyเมธอด


123

โซลูชันสำหรับ PostgreSQL:

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

หรือ

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table " + 
        "where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

1
ดูดี. ส่วนใดของรหัสนี้เฉพาะ PostreSQL "where search_column = ANY (?)"? หรือ connection.createArrayOf หรืออย่างอื่น?
David Portabella

1
ฉันคิดว่าเป็น JDBC4 เฉพาะเจาะจงมากกว่า PostgreSQL เฉพาะเพราะ.createArrayOf()ส่วนหนึ่ง แต่ฉันไม่แน่ใจว่าความหมายที่เข้มงวดสำหรับผู้ใช้นั้นArrayถูกกำหนดโดยข้อกำหนด JDBC
lvella

3
ถ้า.createArrayOfไม่ได้ทำงานที่คุณสามารถทำสร้างคู่มือของคุณเองของอาร์เรย์ที่แท้จริงเช่นString arrayLiteral = "{A,\"B \", C,D}" (หมายเหตุว่า "B" มีพื้นที่ในขณะที่ C ไม่ได้)แล้วstatement.setString(1,arrayLiteral)ที่งบเตรียมเป็นหรือ... IN (SELECT UNNEST(?::VARCHAR[])) ... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))(PS: ฉันไม่คิดว่าจะANYทำงานร่วมกับ a SELECT.)
ADTC

สุดยอดทางออก! ช่วยชีวิตฉันไว้จริงๆ สำหรับอาร์เรย์จำนวนเต็มฉันใช้ "int" ในพารามิเตอร์แรกของ createArrayOf () และมันก็ดูดี พารามิเตอร์แรกนั้นปรากฏเฉพาะฐานข้อมูลโดยอ้างอิงจากเอกสารว่า
Emmanuel Touzery

2
ดูเหมือนว่าวิธีการแก้ปัญหาที่สะอาดที่สุด หากใครกำลังมองหาไวยากรณ์เฉพาะ HSQLDB: ฉันจัดการเพื่อให้มันทำงานกับ IN (UNNEST (?))
aureianimus

19

ไม่มี AFAIK วิธีง่ายๆ หากเป้าหมายคือการรักษาอัตราส่วนงบแคชให้สูง (เช่นไม่สร้างคำสั่งต่อการนับพารามิเตอร์ทุกครั้ง) คุณสามารถทำสิ่งต่อไปนี้:

  1. สร้างคำสั่งที่มีพารามิเตอร์น้อย (เช่น 10):

    ... อยู่ที่ไหน (?,?,?,?,?,?,?,?,?,?,?) ...

  2. ผูกพารามิเตอร์ actuall ทั้งหมด

    setString (1, "foo"); setString (2, "บาร์");

  3. ผูกส่วนที่เหลือเป็น NULL

    setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)

NULL ไม่ตรงกับสิ่งใดเลยดังนั้นจึงได้รับการปรับให้เหมาะสมโดยตัวสร้างแผน SQL

ตรรกะเป็นเรื่องง่ายโดยอัตโนมัติเมื่อคุณส่งรายการไปยังฟังก์ชัน DAO:

while( i < param.size() ) {
  ps.setString(i+1,param.get(i));
  i++;
}

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}

"NULL ไม่ตรงกับสิ่งใด" - NULLในแบบสอบถามจะตรงกับNULLค่าในฐานข้อมูลหรือไม่
Craig McQueen

5
@CraigMcQueen ไม่เป็นไร Null ไม่ได้จับคู่เป็นโมฆะตามมาตรฐาน ANSI
Dawood ibn Kareem

คุณสามารถจับคู่ NULL โดยใช้คำสำคัญ IS NULL วิธีที่ดีในการตรวจสอบแถวที่ไม่มีอยู่ในตารางที่เข้าร่วมคือการใช้ LEFT JOIN ร่วมกับ IS NULL 'เลือก a.URL, b.URL จาก Table_A ซ้ายเข้าร่วม TABLE_B b บน a_A.URL = b_B.URL WHERE b.URL ว่างเปล่า' นี่จะแสดงแถวทั้งหมดในตาราง A ที่ไม่มีการจับคู่ในตาราง B
Jens Tandstad

3
ระวังด้วยว่า NOT INและINห้ามจัดการค่า null ด้วยวิธีเดียวกัน เรียกใช้สิ่งนี้และดูว่าเกิดอะไรขึ้น: select 'Matched' as did_it_match where 1 not in (5, null); จากนั้นลบnullและดูความมหัศจรรย์
แบรนดอน

หรือคุณสามารถตั้งค่าพารามิเตอร์พิเศษทั้งหมดเป็นค่าของพารามิเตอร์ก่อนหน้านี้ เอ็นจิ้น DB ที่เหมาะสมใด ๆ จะกรองออก ดังนั้นเป็นเช่นเดียวกับa IN (1,2,3,3,3,3,3) a IN (1,2,3)มันยังทำงานได้NOT INเหมือนกันa NOT IN (1,2,3,null,null,null,null)(ซึ่งจะไม่ส่งคืนแถวเหมือนที่any_value != NULLเป็นเท็จเสมอ)
Ruslan Stelmachenko

11

การแก้ไขที่ไม่พึงประสงค์ แต่เป็นไปได้อย่างแน่นอนคือการใช้แบบสอบถามที่ซ้อนกัน สร้าง MYVALUES ตารางชั่วคราวด้วยคอลัมน์ในนั้น แทรกรายการค่าของคุณลงในตาราง MYVALUES จากนั้นดำเนินการ

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

น่าเกลียด แต่เป็นทางเลือกที่ทำงานได้หากรายการค่าของคุณมีขนาดใหญ่มาก

เทคนิคนี้มีข้อได้เปรียบที่เพิ่มขึ้นของแผนแบบสอบถามที่ดีขึ้นจากเครื่องมือเพิ่มประสิทธิภาพ (ตรวจสอบหน้าสำหรับค่าหลาย ๆ ค่าสามารถสแกนหนึ่งครั้งต่อหนึ่งครั้งต่อหนึ่งค่า ฯลฯ เท่านั้น) อาจประหยัดค่าใช้จ่ายหากฐานข้อมูลของคุณไม่ได้จัดทำงบ "INSERTS" ของคุณจะต้องทำในแบทช์และตาราง MYVALUES อาจต้องมีการปรับแต่งเพื่อให้มีการล็อคขั้นต่ำหรือการป้องกันค่าใช้จ่ายสูงอื่น ๆ


มีข้อได้เปรียบอะไรบ้างที่มีการค้นหาค่าหนึ่งค่า my_table ทีละครั้ง?
Paul Tomblin

3
เครื่องมือเพิ่มประสิทธิภาพคิวรีสามารถลดการโหลด I / O ได้โดยดึงข้อมูลการจับคู่ที่เป็นไปได้ทั้งหมดจากหน้าเว็บที่โหลด Tablescans หรือการสแกนดัชนีอาจดำเนินการครั้งเดียวแทนค่าหนึ่งครั้งต่อหนึ่งค่า ค่าโสหุ้ยสำหรับการแทรกค่าสามารถลดลงได้ด้วยการดำเนินการแบทช์และอาจน้อยกว่าแบบสอบถามหลายรายการ
James Schek

1
มันดูดี แต่อาจมีปัญหากับการเกิดพร้อมกัน ข้อมูลจำเพาะ jdbc ไม่เห็นด้วยวิธีการสร้างตารางที่ไม่ระบุชื่อชั่วคราวในหน่วยความจำ? หรืออะไรทำนองนั้นถ้าเป็นไปได้ไม่ใช่ผู้ขาย jdbc โดยเฉพาะ?
David Portabella

9

ข้อ จำกัด ของตัวดำเนินการ in () คือรากของความชั่วทั้งหมด

มันใช้งานได้กับกรณีเล็ก ๆ น้อย ๆ และคุณสามารถขยายได้ด้วย "การสร้างคำสั่งที่เตรียมไว้โดยอัตโนมัติ" แต่ก็มีข้อ จำกัด อยู่เสมอ

  • หากคุณกำลังสร้างคำสั่งที่มีพารามิเตอร์จำนวนตัวแปรที่จะทำให้ค่าใช้จ่ายในการแยกวิเคราะห์ sql ในแต่ละสาย
  • บนแพลตฟอร์มจำนวนมากจำนวนพารามิเตอร์ของตัวดำเนินการ in () มี จำกัด
  • บนแพลตฟอร์มทั้งหมดขนาดตัวอักษร SQL โดยรวมจะถูก จำกัด ทำให้ไม่สามารถส่งตัวยึด 2000 รายการสำหรับพารามิเตอร์ภายใน
  • การส่งตัวแปรการเชื่อมโยงที่มีขนาด 1,000-10k เป็นไปไม่ได้เนื่องจากไดรเวอร์ JDBC มีข้อ จำกัด

วิธี In () อาจดีพอสำหรับบางกรณี แต่ไม่สามารถพิสูจน์ได้ด้วยจรวด :)

วิธีการแก้ปัญหาจรวดคือการส่งผ่านจำนวนพารามิเตอร์โดยพลการในการโทรแยกต่างหาก (โดยผ่านการอุดตันของ params เช่น) แล้วมีมุมมอง (หรือวิธีอื่นใด) เพื่อเป็นตัวแทนของพวกเขาใน SQL และใช้ในที่ของคุณ เกณฑ์

ตัวแปรแปรผันกำลังมาที่นี่http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

อย่างไรก็ตามถ้าคุณสามารถใช้ PL / SQL, ระเบียบนี้สามารถสวยเรียบร้อย

function getCustomers(in_customerIdList clob) return sys_refcursor is 
begin
    aux_in_list.parse(in_customerIdList);
    open res for
        select * 
        from   customer c,
               in_list v
        where  c.customer_id=v.token;
    return res;
end;

จากนั้นคุณสามารถส่งหมายเลขรหัสลูกค้าที่คั่นด้วยเครื่องหมายจุลภาคโดยพลการในพารามิเตอร์และ:

  • จะไม่ล่าช้าในการแยกวิเคราะห์เนื่องจาก SQL สำหรับการเลือกมีความเสถียร
  • ไม่มีความซับซ้อนของฟังก์ชั่น pipelined - มันเป็นเพียงแค่แบบสอบถามเดียว
  • SQL กำลังใช้การรวมอย่างง่ายแทนที่จะเป็นตัวดำเนินการ IN ซึ่งค่อนข้างเร็ว
  • ท้ายที่สุดมันเป็นกฎง่ายๆที่ไม่ต้องกดปุ่มฐานข้อมูลด้วยการเลือกแบบธรรมดาหรือ DML เนื่องจากเป็น Oracle ซึ่งให้แสงมากกว่า MySQL หรือฐานข้อมูลอย่างง่าย PL / SQL ช่วยให้คุณสามารถซ่อนรูปแบบการจัดเก็บจากรูปแบบโดเมนแอปพลิเคชันของคุณในวิธีที่มีประสิทธิภาพ

เคล็ดลับที่นี่คือ:

  • เราต้องการการโทรที่รับสายยาวและเก็บที่ใดก็ได้ที่เซสชัน db สามารถเข้าถึงได้ (เช่นตัวแปรแพ็กเกจแบบง่ายหรือ dbms_session.set_context)
  • จากนั้นเราต้องการมุมมองที่สามารถแยกวิเคราะห์สิ่งนี้เป็นแถว
  • และจากนั้นคุณมีมุมมองที่มีรหัสที่คุณกำลังสอบถามดังนั้นสิ่งที่คุณต้องมีคือการเข้าร่วมตารางอย่างง่าย ๆ

มุมมองดูเหมือนว่า:

create or replace view in_list
as
select
    trim( substr (txt,
          instr (txt, ',', 1, level  ) + 1,
          instr (txt, ',', 1, level+1)
             - instr (txt, ',', 1, level) -1 ) ) as token
    from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1

โดยที่ aux_in_list.getpayload อ้างถึงสตริงอินพุตดั้งเดิม


แนวทางที่เป็นไปได้คือการส่งผ่านอาร์เรย์ pl / sql (รองรับโดย Oracle เท่านั้น) แต่คุณไม่สามารถใช้ใน SQL จริงได้ดังนั้นจึงจำเป็นต้องมีขั้นตอนการแปลงเสมอ การแปลงไม่สามารถทำได้ใน SQL ดังนั้นหลังจากทั้งหมดผ่าน clob กับพารามิเตอร์ทั้งหมดในสตริงและแปลงด้วยปัญญาในมุมมองเป็นทางออกที่มีประสิทธิภาพมากที่สุด


6

นี่คือวิธีที่ฉันจะแก้ไขมันในแอปพลิเคชันของฉันเอง ตามหลักแล้วคุณควรใช้ StringBuilder แทนการใช้ + สำหรับสตริง

    String inParenthesis = "(?";
    for(int i = 1;i < myList.size();i++) {
      inParenthesis += ", ?";
    }
    inParenthesis += ")";

    try(PreparedStatement statement = SQLite.connection.prepareStatement(
        String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
      int x = 1;
      statement.setLong(x++, race.startTime);
      statement.setString(x++, race.name);
      statement.setInt(x++, traderIdx);

      for(String str : race.betFair.winners) {
        statement.setString(x++, str);
      }

      int effected = statement.executeUpdate();
    }

การใช้ตัวแปรเช่น x ด้านบนแทนตัวเลขที่เป็นรูปธรรมช่วยได้มากหากคุณตัดสินใจที่จะเปลี่ยนแบบสอบถามในภายหลัง


5

ฉันไม่เคยลอง แต่จะ. setArray () ทำในสิ่งที่คุณต้องการหรือไม่

อัปเดต : ไม่ชัด setArray ดูเหมือนว่าจะทำงานกับ java.sql.Array ที่มาจากคอลัมน์ ARRAY ที่คุณดึงมาจากแบบสอบถามก่อนหน้าหรือแบบสอบถามย่อยด้วยคอลัมน์ ARRAY


4
ไม่ทำงานกับฐานข้อมูลทั้งหมด แต่เป็นวิธีที่ "ถูกต้อง"
skaffman

คุณหมายถึงไดรเวอร์ทั้งหมด ไดรเวอร์บางตัวมีมาตรฐานเทียบเท่าของปีนี้ (ศตวรรษที่แล้ว) อีกวิธีหนึ่งคือการบึงชุดของค่าลงในตารางชั่วคราว แต่ไม่สนับสนุนฐานข้อมูลทั้งหมดที่ ...
ทอม Hawtin - tackline

java.sun.com/j2se/1.3/docs/guide/jdbc/getstart/ ......ตามที่ Sun เนื้อหา Array [โดยทั่วไป] ยังคงอยู่ในฝั่งเซิร์ฟเวอร์และถูกดึงตามความจำเป็น PreparedStatement.setArray () สามารถส่ง Array กลับจาก ResultSet ก่อนหน้านี้ไม่สร้าง Array ใหม่ทางฝั่งไคลเอ็นต์
Chris Mazzola

5

วิธีแก้ปัญหาของฉันคือ:

create or replace type split_tbl as table of varchar(32767);
/

create or replace function split
(
  p_list varchar2,
  p_del varchar2 := ','
) return split_tbl pipelined
is
  l_idx    pls_integer;
  l_list    varchar2(32767) := p_list;
  l_value    varchar2(32767);
begin
  loop
    l_idx := instr(l_list,p_del);
    if l_idx > 0 then
      pipe row(substr(l_list,1,l_idx-1));
      l_list := substr(l_list,l_idx+length(p_del));
    else
      pipe row(l_list);
      exit;
    end if;
  end loop;
  return;
end split;
/

ตอนนี้คุณสามารถใช้หนึ่งตัวแปรเพื่อรับค่าบางอย่างในตาราง:

select * from table(split('one,two,three'))
  one
  two
  three

select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
  value1 AAA
  value2 BBB

ดังนั้นคำสั่งที่เตรียมไว้อาจเป็น:

  "select * from TABLE where COL in (select * from table(split(?)))"

ความนับถือ,

Javier Ibanez


นี่คือ PL / SQL ใช่ มันจะไม่ทำงานในฐานข้อมูลอื่น ๆ โปรดทราบว่าการใช้งานนี้มีข้อ จำกัด ของอินพุต params - ความยาวทั้งหมด จำกัด อยู่ที่ 32k chars - รวมถึงข้อ จำกัด ด้านประสิทธิภาพเนื่องจากการเรียกไปยังฟังก์ชัน pipelined ทำให้การสลับบริบทระหว่าง PL / SQL และ SQL engine ของ Oracle
Gee Bee

3

ฉันคิดว่าคุณสามารถ (ใช้การจัดการสตริงขั้นพื้นฐาน) สร้างสตริงแบบสอบถามในPreparedStatementเพื่อให้มีจำนวน?ตรงกับจำนวนรายการในรายการของคุณ

แน่นอนว่าถ้าคุณทำแบบนั้นคุณแค่เพียงก้าวออกไปจากการสร้างสายโซ่ยักษ์ORในการสืบค้นของคุณ แต่โดยไม่ต้องมีจำนวนที่เหมาะสม?ในสตริงการสืบค้นฉันไม่เห็นว่าคุณจะสามารถแก้ไขได้อย่างไร


ไม่ใช่วิธีแก้ปัญหาสำหรับฉันจริงๆเพราะฉันต้องการส่งในจำนวนที่ต่างกัน ทุกครั้งที่ฉันเรียก ps แต่อย่าคิดว่าฉันไม่ได้พิจารณามัน : P
Chris Mazzola

4
การแฮ็กอื่น: คุณสามารถใช้ตัวยึดพารามิเตอร์จำนวนมาก - ได้มากที่สุดเท่าที่คุณจะมีรายการค่าที่ยาวที่สุด - และหากรายการค่าของคุณสั้นกว่าคุณสามารถทำซ้ำค่าได้: ... WHERE searchfield IN (? ,?,?,?,?,?,?,) และจากนั้นให้ค่า: A, B, C, D, A, B, C, D
Bill Karwin

1
แต่โดยรวมแล้วฉันชอบโซลูชันของอดัม: สร้าง SQL แบบไดนามิกและเชื่อมโยงกันหรือไม่ ตัวแทนเพื่อให้ตรงกับจำนวนของค่าที่คุณต้องผ่าน
Bill Karwin

Bill โซลูชันนั้นใช้งานได้หากฉันไม่ต้องการใช้ PreparedStatement ซ้ำ อีกวิธีหนึ่งคือการทำให้พารามิเตอร์เดียวเรียกหลายครั้งและสะสมผลลัพธ์ในฝั่งไคลเอ็นต์ มีแนวโน้มว่าจะมีประสิทธิภาพมากขึ้นในการสร้าง / ดำเนินการคำสั่งใหม่ด้วยจำนวนที่กำหนดเองใช่หรือไม่ แต่ละครั้งว่า
Chris Mazzola

3

คุณสามารถใช้วิธี setArray ตามที่กล่าวไว้ในjavadoc นี้ :

PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();

2
นี่ไม่รองรับไดรเวอร์ทั้งหมดหากคุณสมบัตินี้ไม่รองรับคุณจะได้รับ SQLFeatureNotSupportedException
ไม่ได้ตั้งชื่อ

น่าเสียดายที่คนขับของฉันไม่รองรับมัน
EdXX

3

คุณสามารถใช้Collections.nCopiesเพื่อสร้างคอลเลกชันของตัวยึดตำแหน่งและเข้าร่วมได้โดยใช้String.join:

List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try (   Connection connection = getConnection();
        PreparedStatement ps = connection.prepareStatement(sql)) {
    int i = 1;
    for (String param : params) {
        ps.setString(i++, param);
    }
    /*
     * Execute query/do stuff
     */
}

ดูเหมือนว่าจะเป็นทางออกที่ดีที่สุดเมื่อใช้ Oracle JDBC ...
jansohn

2

นี่เป็นโซลูชั่นที่สมบูรณ์แบบใน Java เพื่อสร้างคำสั่งที่เตรียมไว้ให้คุณ:

/*usage:

Util u = new Util(500); //500 items per bracket. 
String sqlBefore  = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); 
string sqlAfter = ") and foo = 'bar'"; 

PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/



import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Util {

    private int numValuesInClause;

    public Util(int numValuesInClause) {
        super();
        this.numValuesInClause = numValuesInClause;
    }

    public int getNumValuesInClause() {
        return numValuesInClause;
    }

    public void setNumValuesInClause(int numValuesInClause) {
        this.numValuesInClause = numValuesInClause;
    }

    /** Split a given list into a list of lists for the given size of numValuesInClause*/
    public List<List<Integer>> splitList(
            List<Integer> values) {


        List<List<Integer>> newList = new ArrayList<List<Integer>>(); 
        while (values.size() > numValuesInClause) {
            List<Integer> sublist = values.subList(0,numValuesInClause);
            List<Integer> values2 = values.subList(numValuesInClause, values.size());   
            values = values2; 

            newList.add( sublist);
        }
        newList.add(values);

        return newList;
    }

    /**
     * Generates a series of split out in clause statements. 
     * @param sqlBefore ""select * from dual where ("
     * @param values [1,2,3,4,5,6,7,8,9,10]
     * @param "sqlAfter ) and id = 5"
     * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
     */
    public String genInClauseSql(String sqlBefore, List<Integer> values,
            String sqlAfter, String identifier) 
    {
        List<List<Integer>> newLists = splitList(values);
        String stmt = sqlBefore;

        /* now generate the in clause for each list */
        int j = 0; /* keep track of list:newLists index */
        for (List<Integer> list : newLists) {
            stmt = stmt + identifier +" in (";
            StringBuilder innerBuilder = new StringBuilder();

            for (int i = 0; i < list.size(); i++) {
                innerBuilder.append("?,");
            }



            String inClause = innerBuilder.deleteCharAt(
                    innerBuilder.length() - 1).toString();

            stmt = stmt + inClause;
            stmt = stmt + ")";


            if (++j < newLists.size()) {
                stmt = stmt + " OR ";
            }

        }

        stmt = stmt + sqlAfter;
        return stmt;
    }

    /**
     * Method to convert your SQL and a list of ID into a safe prepared
     * statements
     * 
     * @throws SQLException
     */
    public PreparedStatement prepareStatements(String sqlBefore,
            ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
            throws SQLException {

        /* First split our potentially big list into lots of lists */
        String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
        PreparedStatement ps = c.prepareStatement(stmt);

        int i = 1;
        for (int val : values)
        {

            ps.setInt(i++, val);

        }
        return ps;

    }

}

2

Spring อนุญาตให้ส่งผ่าน java.util.Lists ไปยัง NamedParameterJdbcTemplateซึ่งทำการสร้างรุ่นของ (?,?,?, ... ,?) ตามความเหมาะสมกับจำนวนอาร์กิวเมนต์

สำหรับ Oracle การโพสต์บล็อกนี้กล่าวถึงการใช้ oracle.sql.ARRAY (Connection.createArrayOf ไม่ทำงานกับ Oracle) สำหรับสิ่งนี้คุณต้องแก้ไขคำสั่ง SQL ของคุณ:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

ฟังก์ชันตาราง oracleแปลงอาร์เรย์ผ่านลงในตารางเช่นสามารถใช้งานได้คุ้มค่าในINคำสั่ง


1

ลองใช้ฟังก์ชั่น instr?

select my_column from my_table where  instr(?, ','||search_column||',') > 0

แล้วก็

ps.setString(1, ",A,B,C,"); 

เป็นที่ยอมรับว่านี่เป็นบิตของแฮ็คสกปรก แต่จะลดโอกาสในการฉีด SQL ทำงานใน oracle ต่อไป


โอ้และฉันรู้ว่ามันจะไม่ใช้ดัชนี
stjohnroe

มันจะไม่ทำงานสำหรับสตริงบางตัวอย่างเช่นถ้าสตริงมี ','
David Portabella

1

Sormulaรองรับตัวดำเนินการ SQL IN โดยอนุญาตให้คุณจัดหาวัตถุ java.util.Collection เป็นพารามิเตอร์ มันสร้างคำสั่งที่เตรียมไว้ด้วย? สำหรับแต่ละองค์ประกอบของคอลเลกชัน ดูตัวอย่างที่ 4 ( ตัวอย่าง SQL คือความคิดเห็นเพื่อชี้แจงสิ่งที่สร้างขึ้น แต่ไม่ได้ใช้โดย Sormula)


1

แทนการใช้

SELECT my_column FROM my_table where search_column IN (?)

ใช้คำสั่ง Sql เป็น

select id, name from users where id in (?, ?, ?)

และ

preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');

หรือใช้โพรซีเดอร์ที่เก็บนี่จะเป็นทางออกที่ดีที่สุดเนื่องจากคำสั่ง sql จะรวบรวมและเก็บไว้ในเซิร์ฟเวอร์ DataBase


1

ฉันเจอข้อ จำกัด จำนวนหนึ่งที่เกี่ยวข้องกับคำสั่งที่เตรียมไว้:

  1. คำสั่งที่เตรียมไว้จะถูกแคชภายในเซสชันเดียวกันเท่านั้น (Postgres) ดังนั้นมันจะทำงานได้เฉพาะกับการรวมการเชื่อมต่อเท่านั้น
  2. มีงบที่เตรียมไว้แตกต่างกันมากมายตามที่เสนอโดย @BalusC อาจทำให้แคชล้นและคำสั่งที่แคชไว้ก่อนหน้านี้จะถูกลบ
  3. แบบสอบถามจะต้องมีการปรับให้เหมาะสมและใช้ดัชนี ฟังดูชัดเจนอย่างไรก็ตามเช่นคำสั่ง ANY (ARRAY ... ) ที่เสนอโดย @Boris ในหนึ่งในคำตอบยอดนิยมไม่สามารถใช้ดัชนีและการสืบค้นจะช้าแม้จะมีการแคช
  4. คำสั่งที่เตรียมไว้จะแคชแผนแบบสอบถามด้วยและค่าจริงของพารามิเตอร์ใด ๆ ที่ระบุในคำสั่งนั้นไม่พร้อมใช้งาน

ในบรรดาโซลูชันที่เสนอฉันจะเลือกโซลูชันที่ไม่ลดประสิทธิภาพการสืบค้นและทำให้จำนวนการค้นหาน้อยลง นี่จะเป็น # 4 (แบทช์แบบสอบถามจำนวนเล็กน้อย) จากลิงก์ @Don หรือระบุค่า NULL สำหรับไม่ต้องการ '?' เครื่องหมายตามที่เสนอโดย @Vladimir Dyuzhev


1

ฉันเพิ่งใช้งานตัวเลือกเฉพาะ PostgreSQL สำหรับสิ่งนี้ มันเป็นบิตของการแฮ็คและมาพร้อมกับข้อดีข้อเสียและข้อ จำกัด ของตัวเอง แต่ดูเหมือนว่าจะใช้งานได้และไม่ได้ จำกัด เฉพาะภาษาการพัฒนาแพลตฟอร์มหรือไดรเวอร์ PG ที่เฉพาะเจาะจง

เคล็ดลับของหลักสูตรคือการหาวิธีที่จะผ่านการรวบรวมความยาวโดยพลการของค่าเป็นพารามิเตอร์เดียวและมี db รับรู้มันเป็นหลายค่า วิธีแก้ปัญหาที่ฉันได้ทำงานคือการสร้างสตริงที่มีตัวคั่นจากค่าในคอลเลกชันผ่านสตริงนั้นเป็นพารามิเตอร์เดียวและใช้ string_to_array () ด้วยการคัดเลือกสิ่งที่จำเป็นสำหรับ PostgreSQL เพื่อให้สามารถใช้งานได้อย่างถูกต้อง

ดังนั้นหากคุณต้องการค้นหาคำว่า "foo", "blah" และ "abc" คุณสามารถต่อสายเข้าด้วยกันเป็นสตริงเดียวเช่น 'foo, blah, abc' นี่คือ SQL ตรง:

select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);

เห็นได้ชัดว่าคุณจะเปลี่ยน cast cast อย่างชัดเจนเป็นสิ่งที่คุณต้องการให้อาร์เรย์ค่าผลลัพธ์ของคุณเป็น - int, text, uuid, ฯลฯ และเนื่องจากฟังก์ชันใช้ค่าสตริงเดี่ยว (หรือสองอย่างที่ฉันคิดว่าถ้าคุณต้องการกำหนดค่าตัวคั่น เช่นกัน) คุณสามารถผ่านมันเป็นพารามิเตอร์ในคำสั่งที่เตรียมไว้:

select column from table
where search_column = any (string_to_array($1, ',')::text[]);

มันมีความยืดหยุ่นเพียงพอที่จะรองรับสิ่งต่าง ๆ เช่นการเปรียบเทียบแบบ LIKE:

select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);

อีกครั้งไม่มีคำถามว่าเป็นแฮ็ก แต่ก็ใช้งานได้และอนุญาตให้คุณใช้คำสั่งที่เตรียมไว้ล่วงหน้าซึ่งใช้พารามิเตอร์ที่ไม่ต่อเนื่อง* ahem *พร้อมการรักษาความปลอดภัยและประโยชน์ (อาจ) แนะนำหรือไม่ ขึ้นอยู่กับว่าคุณมีการวิเคราะห์สตริงและอาจส่งต่อไปก่อนที่แบบสอบถามของคุณจะทำงาน หากคุณคาดหวังว่าจะส่งค่าสาม, ห้า, สิบสองสามค่าแน่นอนว่าอาจเป็นไปได้ สองสามพัน ใช่อาจจะไม่มาก YMMV ข้อ จำกัด และการยกเว้นนี้ไม่มีการรับประกันโดยชัดแจ้งหรือโดยนัย

แต่มันได้ผล


0

เพื่อความสมบูรณ์: ตราบใดที่ชุดของค่าไม่ใหญ่เกินไปคุณก็สามารถสร้างคำสั่งแบบสตริงได้เช่นกัน

... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?

ซึ่งคุณสามารถส่งผ่านเพื่อเตรียม () จากนั้นใช้ setXXX () ในลูปเพื่อตั้งค่าทั้งหมด นี่ดูน่าชัง แต่ระบบการค้า "ใหญ่" หลายอย่างมักทำสิ่งนี้จนกว่าจะถึงขีด จำกัด เฉพาะของฐานข้อมูลเช่น 32 KB (ฉันคิดว่ามันเป็น) สำหรับงบใน Oracle

แน่นอนคุณต้องมั่นใจว่าชุดจะไม่มีขนาดใหญ่เกินสมควรหรือทำผิดพลาดในกรณีที่เป็น


ใช่คุณพูดถูก. เป้าหมายของฉันในกรณีนี้คือการใช้ PreparedStatement ที่มีจำนวนรายการต่างกันในแต่ละครั้ง
Chris Mazzola

3
การใช้ "หรือ" จะทำให้เข้าใจผิดเจตนา ใช้ "IN" ในการอ่านง่ายขึ้นและจุดประสงค์ชัดเจนยิ่งขึ้น เหตุผลเดียวที่จะเปลี่ยนคือถ้าแผนการสืบค้นแตกต่างกัน
James Schek

0

ติดตามความคิดของอดัม ทำให้คำสั่งที่เตรียมไว้ของคุณเลือก my_column จาก my_table โดยที่ search_column ใน (#) สร้าง String x และเติมด้วยตัวเลข "?,?,?" ขึ้นอยู่กับรายการค่าของคุณจากนั้นเพียงเปลี่ยน # ในแบบสอบถามสำหรับการเติมสตริง x ใหม่ของคุณ


0

สร้างสตริงแบบสอบถามใน PreparedStatement เพื่อให้มีจำนวนรายการที่ตรงกับจำนวนรายการในรายการของคุณ นี่คือตัวอย่าง:

public void myQuery(List<String> items, int other) {
  ...
  String q4in = generateQsForIn(items.size());
  String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?";
  PreparedStatement ps = connection.prepareStatement(sql);
  int i = 1;
  for (String item : items) {
    ps.setString(i++, item);
  }
  ps.setInt(i++, other);
  ResultSet rs = ps.executeQuery();
  ...
}

private String generateQsForIn(int numQs) {
    String items = "";
    for (int i = 0; i < numQs; i++) {
        if (i != 0) items += ", ";
        items += "?";
    }
    return items;
}

5
ไม่จำเป็นต้องใช้ StringBuilder อีกต่อไป คอมไพเลอร์แปลงเครื่องหมาย + เป็น StringBuilder.append () อย่างไรก็ตามจึงไม่มีประสิทธิภาพในการทำงาน ลองด้วยตัวคุณเอง :)
neu242

5
@ neu242: StringBuilderโอ้ใช่การใช้คอมไพเลอร์ แต่ไม่ใช่ในแบบที่คุณคิด การคอมไพล์generateQsForInคุณจะเห็นว่าการวนซ้ำซ้ำสองครั้งStringBuilderจะถูกจัดสรรและtoStringถูกเรียกใช้ในแต่ละครั้ง การปรับให้StringBuilderเหมาะสมจับสิ่งต่าง ๆ เช่น"x" + i+ "y" + jเท่านั้น แต่ไม่ขยายเกินหนึ่งนิพจน์
AH

@ neu242 คุณไม่สามารถใช้ps.setObject(1,items)แทนการวนซ้ำในรายการแล้วตั้งค่าparamteresหรือไม่
Neha Choudhary

0

มีวิธีการทางเลือกที่แตกต่างกันซึ่งเราสามารถใช้สำหรับประโยค IN ใน PreparedStatement

  1. การใช้การค้นหาครั้งเดียว - ประสิทธิภาพที่ช้าที่สุดและทรัพยากรเข้มข้น
  2. ใช้ StoredProcedure - เร็วที่สุด แต่เฉพาะฐานข้อมูล
  3. การสร้างแบบสอบถามแบบไดนามิกสำหรับ PreparedStatement - ประสิทธิภาพที่ดี แต่ไม่ได้รับประโยชน์จากการแคชและ PreparedStatement จะถูกคอมไพล์ใหม่ทุกครั้ง
  4. ใช้ NULL ในคิวรี PreparedStatement - ประสิทธิภาพที่ดีที่สุดทำงานได้ดีเมื่อคุณทราบถึงข้อ จำกัด IN clause หากไม่มีข้อ จำกัด คุณสามารถดำเนินการแบบสอบถามเป็นชุด ตัวอย่างโค้ดคือ;

        int i = 1;
        for(; i <=ids.length; i++){
            ps.setInt(i, ids[i-1]);
        }
    
        //set null for remaining ones
        for(; i<=PARAM_SIZE;i++){
            ps.setNull(i, java.sql.Types.INTEGER);
        }

คุณสามารถตรวจสอบรายละเอียดเพิ่มเติมเกี่ยวกับทางเลือกเหล่านี้วิธีการที่นี่


"การสร้างเคียวรีแบบไดนามิกสำหรับ PreparedStatement - ประสิทธิภาพที่ดี แต่ไม่ได้ประโยชน์จากการแคชและ PreparedStatement จะถูกคอมไพล์ใหม่ทุกครั้ง" แคชและหลีกเลี่ยงการคอมไพล์ซ้ำเป็นสิ่งที่ทำให้คำสั่งที่เตรียมไว้ทำงานได้ดี ดังนั้นฉันไม่เห็นด้วยกับการเรียกร้องของคุณ อย่างไรก็ตามสิ่งนี้จะป้องกันการฉีด SQL เนื่องจากคุณมีการ จำกัด อินพุตแบบต่อเนื่อง / ไดนามิกไปยังเครื่องหมายจุลภาค
Brandon

ฉันเห็นด้วยกับคุณอย่างไรก็ตาม "ประสิทธิภาพที่ดี" ที่นี่มีไว้สำหรับสถานการณ์เฉพาะนี้ ประสิทธิภาพดีกว่าวิธีที่ 1 แต่วิธีที่ 2 นั้นเร็วที่สุด
Pankaj

0

สำหรับบางสถานการณ์ regexp อาจช่วยได้ นี่คือตัวอย่างที่ฉันตรวจสอบกับ Oracle และใช้งานได้

select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

แต่มีข้อเสียมากมาย:

  1. คอลัมน์ใด ๆ ที่นำไปใช้ควรถูกแปลงเป็น varchar / char อย่างน้อยโดยปริยาย
  2. ต้องระวังด้วยตัวละครพิเศษ
  3. มันสามารถทำให้ประสิทธิภาพลดลง - ในกรณีของฉันในเวอร์ชันใช้การสแกนดัชนีและช่วงและรุ่น REGEXP ทำการสแกนแบบเต็ม

0

หลังจากตรวจสอบวิธีการแก้ปัญหาต่าง ๆ ในฟอรัมต่าง ๆ และไม่พบวิธีแก้ปัญหาที่ดีฉันรู้สึกว่าแฮ็คด้านล่างที่ฉันคิดไว้นั้นเป็นวิธีที่ง่ายที่สุดในการติดตามและโค้ด

ตัวอย่าง: สมมติว่าคุณมีพารามิเตอร์หลายตัวที่จะส่งผ่านในส่วนคำสั่ง 'IN' เพียงแค่ใส่สตริงหุ่นจำลองในประโยค 'IN' ให้พูดว่า "PARAM" เพื่อแสดงรายการพารามิเตอร์ที่จะเข้ามาแทนที่สตริงหุ่นจำลองนี้

    select * from TABLE_A where ATTR IN (PARAM);

คุณสามารถรวบรวมพารามิเตอร์ทั้งหมดเป็นตัวแปรสตริงเดียวในรหัส Java ของคุณ สามารถทำได้ดังนี้:

    String param1 = "X";
    String param2 = "Y";
    String param1 = param1.append(",").append(param2);

คุณสามารถผนวกพารามิเตอร์ทั้งหมดของคุณคั่นด้วยเครื่องหมายจุลภาคในตัวแปรสตริงเดียว 'param1' ในกรณีของเรา

หลังจากรวบรวมพารามิเตอร์ทั้งหมดเป็นสตริงเดียวคุณก็สามารถแทนที่ข้อความจำลองในแบบสอบถามของคุณเช่น "PARAM" ในกรณีนี้ด้วยพารามิเตอร์ String คือ param1 นี่คือสิ่งที่คุณต้องทำ:

    String query = query.replaceFirst("PARAM",param1); where we have the value of query as 

    query = "select * from TABLE_A where ATTR IN (PARAM)";

ตอนนี้คุณสามารถเรียกใช้คิวรีของคุณโดยใช้วิธี executeQuery () เพียงตรวจสอบให้แน่ใจว่าคุณไม่มีคำว่า "PARAM" ในการค้นหาของคุณทุกที่ คุณสามารถใช้การรวมกันของอักขระพิเศษและตัวอักษรแทนคำว่า "PARAM" เพื่อให้แน่ใจว่าไม่มีความเป็นไปได้ของคำดังกล่าวในการสืบค้น หวังว่าคุณจะมีทางออก

หมายเหตุ: แม้ว่านี่จะไม่ใช่แบบสอบถามที่เตรียมไว้ แต่ก็เป็นงานที่ฉันต้องการให้รหัสของฉันทำ


0

เพียงเพื่อความสมบูรณ์และเพราะฉันไม่เห็นใครแนะนำ:

ก่อนที่จะใช้คำแนะนำที่ซับซ้อนใด ๆ ข้างต้นให้พิจารณาว่าการฉีด SQL เป็นปัญหาในสถานการณ์ของคุณหรือไม่

ในหลายกรณีค่าที่ให้ไว้กับ IN (... ) คือรายการรหัสที่สร้างขึ้นในแบบที่คุณมั่นใจได้ว่าจะไม่สามารถทำการฉีดได้ ... (เช่นผลลัพธ์ของการเลือก some_id ก่อนหน้าจาก some_table โดยที่ some_condition.)

หากเป็นกรณีนี้คุณอาจเชื่อมค่านี้และไม่ใช้บริการหรือคำสั่งที่เตรียมไว้หรือใช้กับพารามิเตอร์อื่น ๆ ของแบบสอบถามนี้

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";

0

PreparedStatement ไม่มีวิธีใดที่ดีในการจัดการกับ SQL IN clause ต่อhttp://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "คุณไม่สามารถทดแทนสิ่งต่าง ๆ ที่จะกลายเป็นส่วนหนึ่งของคำสั่ง SQL ได้นี่เป็นสิ่งจำเป็นเพราะถ้า SQL สามารถเปลี่ยนแปลงได้ ไดรเวอร์ไม่สามารถคอมไพล์คำสั่งล่วงหน้าได้นอกจากนี้ยังมีผลข้างเคียงที่ดีของการป้องกันการโจมตีด้วยการฉีด SQL ฉันสิ้นสุดโดยใช้วิธีการต่อไปนี้:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
    if (hasResults)
        return stmt.getResultSet();

    hasResults = stmt.getMoreResults();

} while (hasResults || stmt.getUpdateCount() != -1);

0

SetArray เป็นทางออกที่ดีที่สุด แต่ไม่สามารถใช้ได้กับไดรเวอร์รุ่นเก่าหลายรุ่น วิธีแก้ปัญหาต่อไปนี้สามารถใช้ได้ใน java8

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"

String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);

//Now create Prepared Statement and use loop to Set entries
int index=1;

for (String input : inputArray) {
     preparedStatement.setString(index++, input);
}

การแก้ปัญหานี้ดีกว่าที่อื่นน่าเกลียดในขณะที่การแก้ปัญหาวนรอบที่สตริงแบบสอบถามถูกสร้างขึ้นโดยการทำซ้ำด้วยตนเอง


0

สิ่งนี้ใช้ได้สำหรับฉัน (psuedocode):

public class SqlHelper
{
    public static final ArrayList<String>platformList = new ArrayList<>(Arrays.asList("iOS","Android","Windows","Mac"));

    public static final String testQuery = "select * from devices where platform_nm in (:PLATFORM_NAME)";
}

ระบุผูกพัน:

public class Test extends NamedParameterJdbcDaoSupport
public List<SampleModelClass> runQuery()
{
    //define rowMapper to insert in object of SampleClass
    final Map<String,Object> map = new HashMap<>();
    map.put("PLATFORM_LIST",DeviceDataSyncQueryConstants.platformList);
    return getNamedParameterJdbcTemplate().query(SqlHelper.testQuery, map, rowMapper)
}

0

ตัวอย่างของฉันสำหรับฐานข้อมูล SQLite และ Oracle

For for loop สำหรับการสร้างออบเจ็กต์ PreparedStatement

ที่สองสำหรับการวนซ้ำสำหรับการจัดหาค่าสำหรับ PreparedStatement พารามิเตอร์

List<String> roles = Arrays.asList("role1","role2","role3");
Map<String, String> menu = getMenu(conn, roles);

public static Map<String, String> getMenu(Connection conn, List<String> roles ) {
    Map<String, String> menu = new LinkedHashMap<String, String>();

    PreparedStatement stmt;
    ResultSet rset;
    String sql;
    try {
        if (roles == null) {
            throw new Exception();
        }
        int size = roles.size();
        if (size == 0) {
            throw new Exception("empty list");
        }
        StringBuilder sb = new StringBuilder();
        sb.append("select page_controller, page_name from pages "
                + " where page_controller in (");
        for (int i = 0; i < size; i++) {
            sb.append("?,");
        }
        sb.setLength(sb.length() - 1);
        sb.append(") order by page_id");
        sql = sb.toString();
        stmt = conn.prepareStatement(sql);
        for (int i = 0; i < size; i++) {
            stmt.setString(i + 1, roles.get(i));
        }
        rset = stmt.executeQuery();
        while (rset.next()) {
            menu.put(rset.getString(1), rset.getString(2));
        }

        conn.close();
    } catch (Exception ex) {
        logger.info(ex.toString());
        try {
            conn.close();
        } catch (SQLException e) {
        }
        return menu;
    }
    return menu;
}

-3

วิธีแก้ปัญหาของฉัน (JavaScript)

    var s1 = " SELECT "

 + "FROM   table t "

 + "  where t.field in ";

  var s3 = '(';

  for(var i =0;i<searchTerms.length;i++)
  {
    if(i+1 == searchTerms.length)
    {
     s3  = s3+'?)';
    }
    else
    {
        s3  = s3+'?, ' ;
    }
   }
    var query = s1+s3;

    var pstmt = connection.prepareStatement(query);

     for(var i =0;i<searchTerms.length;i++)
    {
        pstmt.setString(i+1, searchTerms[i]);
    }

SearchTerms คืออาร์เรย์ที่มีอินพุต / คีย์ / ฟิลด์ ฯลฯ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.