วิธีที่สะอาดที่สุดในการสร้างสตริง SQL ใน Java


108

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

ฉันคิดว่าจะใช้ MessageFormat - แต่มันควรจะใช้สำหรับข้อความของผู้ใช้แม้ว่าฉันคิดว่ามันจะทำงานได้ดี แต่ฉันคิดว่าน่าจะมีอะไรที่สอดคล้องกับการดำเนินการประเภท SQL ในไลบรารี java sql มากกว่า

Groovy จะดีหรือไม่?

คำตอบ:


76

ก่อนอื่นให้พิจารณาใช้พารามิเตอร์การค้นหาในคำสั่งที่เตรียมไว้:

PreparedStatement stm = c.prepareStatement("UPDATE user_table SET name=? WHERE id=?");
stm.setString(1, "the name");
stm.setInt(2, 345);
stm.executeUpdate();

สิ่งอื่นที่สามารถทำได้คือเก็บเคียวรีทั้งหมดไว้ในไฟล์คุณสมบัติ ตัวอย่างเช่นในไฟล์ queries.properties สามารถวางแบบสอบถามด้านบน:

update_query=UPDATE user_table SET name=? WHERE id=?

จากนั้นด้วยความช่วยเหลือของคลาสยูทิลิตี้ง่ายๆ:

public class Queries {

    private static final String propFileName = "queries.properties";
    private static Properties props;

    public static Properties getQueries() throws SQLException {
        InputStream is = 
            Queries.class.getResourceAsStream("/" + propFileName);
        if (is == null){
            throw new SQLException("Unable to load property file: " + propFileName);
        }
        //singleton
        if(props == null){
            props = new Properties();
            try {
                props.load(is);
            } catch (IOException e) {
                throw new SQLException("Unable to load property file: " + propFileName + "\n" + e.getMessage());
            }           
        }
        return props;
    }

    public static String getQuery(String query) throws SQLException{
        return getQueries().getProperty(query);
    }

}

คุณอาจใช้คำถามของคุณดังนี้:

PreparedStatement stm = c.prepareStatement(Queries.getQuery("update_query"));

นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างง่าย แต่ใช้ได้ผลดี


1
ฉันชอบใช้ตัวสร้าง SQL ที่สะอาดเช่นนี้: mentabean.soliveirajr.com
TraderJoeChicago

2
ฉันขอแนะนำให้คุณใส่InputStreamด้านในของif (props == null)คำสั่งเพื่อที่คุณจะได้ไม่สร้างอินสแตนซ์เมื่อไม่จำเป็น
SyntaxRules

64

สำหรับพล SQL ใช้jOOQ jOOQ ขณะนี้สนับสนุนSELECT, INSERT, UPDATE, DELETE, และTRUNCATE MERGEคุณสามารถสร้าง SQL ได้ดังนี้:

String sql1 = DSL.using(SQLDialect.MYSQL)  
                 .select(A, B, C)
                 .from(MY_TABLE)
                 .where(A.equal(5))
                 .and(B.greaterThan(8))
                 .getSQL();

String sql2 = DSL.using(SQLDialect.MYSQL)  
                 .insertInto(MY_TABLE)
                 .values(A, 1)
                 .values(B, 2)
                 .getSQL();

String sql3 = DSL.using(SQLDialect.MYSQL)  
                 .update(MY_TABLE)
                 .set(A, 1)
                 .set(B, 2)
                 .where(C.greaterThan(5))
                 .getSQL();

แทนที่จะได้รับสตริง SQL คุณสามารถดำเนินการได้โดยใช้ jOOQ ดู

http://www.jooq.org

(ข้อจำกัดความรับผิดชอบ: ฉันทำงานให้กับ บริษัท ที่อยู่เบื้องหลัง jOOQ)


ในหลาย ๆ กรณีจะไม่ใช่วิธีแก้ปัญหาที่ไม่ดีเนื่องจากคุณไม่สามารถปล่อยให้ dbms แยกวิเคราะห์คำสั่งล่วงหน้าด้วยค่าต่างๆสำหรับ "5", "8" ฯลฯ ได้หรือไม่? ฉันเดาว่าการรันด้วย jooq จะแก้ปัญหาได้หรือไม่?
Vegard

@Vegard: คุณมีวิธีการควบคุมการjOOQควรทำให้ค่าผูกในการส่งออกของ SQL: jooq.org/doc/3.1/manual/sql-building/bind-values กล่าวอีกนัยหนึ่งคือคุณสามารถเลือกได้ว่าจะแสดงผล"?"หรือจะกำหนดค่าการผูกแบบอินไลน์
Lukas Eder

1
@Vegard: ไม่มีสิ่งใดป้องกันไม่ให้คุณส่งผ่านตัวแปรไปยัง jOOQ API และสร้างคำสั่ง SQL ขึ้นมาใหม่ นอกจากนี้คุณสามารถแยกค่าการผูกตามลำดับโดยใช้jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​()หรือตั้งชื่อค่าการผูกตามชื่อโดยใช้jooq.org/javadoc/latest/org/jooq /Query.html#getParams () คำตอบของฉันมีเพียงตัวอย่างที่เรียบง่าย ... ฉันไม่แน่ใจว่าสิ่งนี้ตอบสนองความกังวลของคุณหรือไม่?
Lukas Eder

2
เป็นทางออกที่มีค่าใช้จ่ายสูง
ตัวเรียงลำดับ

1
อย่าลืมใส่ข้อความปฏิเสธความรับผิดชอบที่ระบุว่าคุณเป็น CEO ของ บริษัท ที่อยู่เบื้องหลัง jOOQ ;)
Stephan

15

เทคโนโลยีหนึ่งที่คุณควรพิจารณาคือSQLJซึ่งเป็นวิธีฝังคำสั่ง SQL โดยตรงใน Java ตัวอย่างง่ายๆคุณอาจมีสิ่งต่อไปนี้ในไฟล์ชื่อ TestQueries.sqlj:

public class TestQueries
{
    public String getUsername(int id)
    {
        String username;
        #sql
        {
            select username into :username
            from users
            where pkey = :id
        };
        return username;
    }
}

มีขั้นตอนการคอมไพล์เพิ่มเติมเพิ่มเติมซึ่งใช้ไฟล์. sqlj ของคุณและแปลเป็น Java บริสุทธิ์ - ในระยะสั้นจะมองหาบล็อกพิเศษที่คั่นด้วย

#sql
{
    ...
}

และเปลี่ยนเป็นการเรียก JDBC มีประโยชน์หลักหลายประการในการใช้ SQLJ:

  • แยกเลเยอร์ JDBC ออกไปโดยสิ้นเชิง - โปรแกรมเมอร์ต้องคิดถึง Java และ SQL เท่านั้น
  • ตัวแปลสามารถตรวจสอบคำค้นหาของคุณสำหรับไวยากรณ์ ฯลฯ เทียบกับฐานข้อมูลในเวลาคอมไพล์
  • ความสามารถในการผูกตัวแปร Java โดยตรงในแบบสอบถามโดยใช้คำนำหน้า ":"

มีการใช้งานตัวแปลสำหรับผู้ให้บริการฐานข้อมูลรายใหญ่ส่วนใหญ่ดังนั้นคุณจะสามารถค้นหาทุกสิ่งที่คุณต้องการได้อย่างง่ายดาย


อันนี้ล้าสมัยแล้วตามวิกิพีเดีย
Zeus

1
ในขณะที่เขียน (ม.ค. 2016) SQLJ ถูกอ้างถึงใน Wikipediaว่า "ล้าสมัย" โดยไม่มีการอ้างอิงใด ๆ มันถูกทิ้งอย่างเป็นทางการหรือไม่? ถ้าใช่ฉันจะติดคำเตือนที่ด้านบนของคำตอบนี้
Ashley Mercer

NB เทคโนโลยียังคงได้รับการสนับสนุนเช่นในรุ่นล่าสุดของ Oracle, 12c ฉันจะยอมรับว่ามันไม่ใช่มาตรฐานที่ทันสมัยที่สุด แต่ก็ยังใช้งานได้และมีประโยชน์บางอย่าง (เช่นการตรวจสอบเวลาคอมไพล์ของแบบสอบถามเทียบกับ DB) ที่ไม่มีในระบบอื่น
Ashley Mercer

12

ฉันสงสัยว่าคุณเป็นคนชอบSquiggleหรือเปล่า นอกจากนี้ยังมีสิ่งที่มีประโยชน์มากคือjDBI แม้ว่าจะไม่ช่วยคุณในการสืบค้น


9

ฉันจะมีลักษณะที่ฤดูใบไม้ผลิ JDBC ฉันใช้เมื่อใดก็ตามที่ฉันต้องการเรียกใช้งาน SQL โดยใช้โปรแกรม ตัวอย่าง:

int countOfActorsNamedJoe
    = jdbcTemplate.queryForInt("select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});

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


ฉันจะรับแบบสอบถาม sql ที่ดำเนินการจริงได้อย่างไร ฉันต้องการบันทึกมัน
kodmanyagha

5

ฉันมักจะใช้พารามิเตอร์ Named JDBC ของ Spring ดังนั้นฉันจึงสามารถเขียนสตริงมาตรฐานเช่น "select * from blah โดยที่ colX = ': someValue'"; คิดว่าน่าอ่านทีเดียว

อีกทางเลือกหนึ่งคือการจัดหาสตริงในไฟล์. sql แยกต่างหากและอ่านเนื้อหาโดยใช้วิธียูทิลิตี้

โอ้คุ้มค่ากับการดู Squill: https://squill.dev.java.net/docs/tutorial.html


ฉันคิดว่าคุณหมายถึงคุณกำลังใช้ BeanPropertySqlParameterSource? ฉันเกือบจะเห็นด้วยกับคุณคลาสที่ฉันเพิ่งพูดถึงนั้นยอดเยี่ยมเมื่อใช้ถั่วอย่างเคร่งครัด แต่อย่างอื่นฉันขอแนะนำให้ใช้ ParameterizedRowMapper ที่กำหนดเองเพื่อสร้างวัตถุ
Esko

ไม่มาก คุณสามารถใช้ SqlParameterSource ใด ๆ กับ Named JDBC Parameters มันเหมาะกับความต้องการของฉันที่จะใช้ MapSqlParameterSource แทนที่จะเป็นพันธุ์ถั่ว ทั้งสองวิธีนี้เป็นทางออกที่ดี อย่างไรก็ตาม RowMappers จัดการกับอีกด้านหนึ่งของปริศนา SQL: เปลี่ยนผลลัพธ์ให้เป็นวัตถุ
GaryF

4

ฉันสองคำแนะนำสำหรับการใช้ ORM เช่น Hibernate อย่างไรก็ตามมีบางสถานการณ์ที่ใช้ไม่ได้ผลดังนั้นฉันจะใช้โอกาสนี้เพื่อพูดถึงบางสิ่งที่ฉันได้ช่วยเขียน: SqlBuilderเป็นไลบรารี java สำหรับการสร้างคำสั่ง sql แบบไดนามิกโดยใช้สไตล์ "builder" มันค่อนข้างทรงพลังและยืดหยุ่นพอสมควร


4

ฉันทำงานกับแอ็พพลิเคชัน Java servlet ที่ต้องการสร้างคำสั่ง SQL แบบไดนามิกมากเพื่อวัตถุประสงค์ในการรายงาน adhoc ฟังก์ชั่นพื้นฐานของแอพนี้คือป้อนพารามิเตอร์คำขอ HTTP ที่มีชื่อจำนวนมากลงในแบบสอบถามที่เข้ารหัสไว้ล่วงหน้าและสร้างตารางผลลัพธ์ที่จัดรูปแบบไว้อย่างสวยงาม ฉันใช้ Spring MVC และเฟรมเวิร์กการฉีดพึ่งพาเพื่อจัดเก็บการสืบค้น SQL ทั้งหมดของฉันในไฟล์ XML และโหลดลงในแอปพลิเคชันการรายงานพร้อมกับข้อมูลการจัดรูปแบบตาราง ในที่สุดข้อกำหนดการรายงานก็ซับซ้อนกว่าความสามารถของเฟรมเวิร์กการแม็ปพารามิเตอร์ที่มีอยู่และฉันต้องเขียนเอง เป็นแบบฝึกหัดที่น่าสนใจในการพัฒนาและสร้างกรอบสำหรับการทำแผนที่พารามิเตอร์ที่มีประสิทธิภาพมากกว่าสิ่งอื่นใดที่ฉันสามารถหาได้

การแมปพารามิเตอร์ใหม่มีลักษณะดังนี้:

select app.name as "App", 
       ${optional(" app.owner as "Owner", "):showOwner}
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = ${integer(0,50):serverId}
   and app.id in ${integerList(50):appId}
 group by app.name, ${optional(" app.owner, "):showOwner} sv.name
 order by app.name, sv.name

ความสวยงามของเฟรมเวิร์กที่ได้คือสามารถประมวลผลพารามิเตอร์คำร้องขอ HTTP ลงในแบบสอบถามได้โดยตรงด้วยการตรวจสอบประเภทที่เหมาะสมและการ จำกัด การตรวจสอบ ไม่จำเป็นต้องมีการแมปเพิ่มเติมสำหรับการตรวจสอบอินพุต ในตัวอย่างแบบสอบถามด้านบนพารามิเตอร์ที่ชื่อserverId จะถูกตรวจสอบเพื่อให้แน่ใจว่าสามารถส่งเป็นจำนวนเต็มและอยู่ในช่วง 0-50 พารามิเตอร์appIdจะถูกประมวลผลเป็นอาร์เรย์ของจำนวนเต็มโดยจำกัดความยาวไว้ที่ 50 หากฟิลด์showOwnerมีอยู่และตั้งค่าเป็น "จริง" บิตของ SQL ในเครื่องหมายคำพูดจะถูกเพิ่มลงในแบบสอบถามที่สร้างขึ้นสำหรับการแมปฟิลด์ที่เป็นทางเลือก ฟิลด์มีการแม็พประเภทพารามิเตอร์อีกมากมายรวมถึงเซ็กเมนต์เสริมของ SQL ที่มีการแม็พพารามิเตอร์เพิ่มเติม ช่วยให้การแมปแบบสอบถามมีความซับซ้อนมากที่สุดเท่าที่นักพัฒนาสามารถคิดขึ้นได้ มันยังมีการควบคุมในการกำหนดค่ารายงานเพื่อกำหนดว่าแบบสอบถามที่ระบุจะมีการแมปขั้นสุดท้ายผ่านทาง PreparedStatement หรือเพียงแค่เรียกใช้เป็นแบบสอบถามที่สร้างไว้ล่วงหน้า

สำหรับค่าคำขอ Http ตัวอย่าง:

showOwner: true
serverId: 20
appId: 1,2,3,5,7,11,13

มันจะสร้าง SQL ต่อไปนี้:

select app.name as "App", 
       app.owner as "Owner", 
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = 20
   and app.id in (1,2,3,5,7,11,13)
 group by app.name,  app.owner,  sv.name
 order by app.name, sv.name

ฉันคิดว่า Spring หรือ Hibernate หรือหนึ่งในกรอบเหล่านั้นควรมีกลไกการทำแผนที่ที่มีประสิทธิภาพมากขึ้นซึ่งตรวจสอบประเภทอนุญาตประเภทข้อมูลที่ซับซ้อนเช่นอาร์เรย์และคุณสมบัติอื่น ๆ ดังกล่าว ฉันเขียนเครื่องยนต์ของฉันเพื่อจุดประสงค์เท่านั้นมันยังไม่ได้อ่านสำหรับรุ่นทั่วไป ขณะนี้ใช้งานได้กับแบบสอบถามของ Oracle เท่านั้นและรหัสทั้งหมดเป็นของ บริษัท ขนาดใหญ่ สักวันฉันอาจใช้ความคิดและสร้างกรอบโอเพนซอร์สใหม่ แต่ฉันหวังว่าหนึ่งในผู้เล่นรายใหญ่ที่มีอยู่จะรับมือกับความท้าทายนี้


3

ทำไมคุณถึงต้องการสร้าง sql ทั้งหมดด้วยมือ? คุณเคยดู ORM เช่น Hibernate หรือไม่ทั้งนี้ขึ้นอยู่กับโครงการของคุณว่าอาจทำสิ่งที่คุณต้องการได้อย่างน้อย 95% ทำด้วยวิธีที่สะอาดกว่าจากนั้นใช้ SQL ดิบและหากคุณต้องการได้รับประสิทธิภาพบิตสุดท้ายคุณสามารถสร้าง แบบสอบถาม SQL ที่ต้องปรับแต่งด้วยมือ


3

คุณสามารถดูที่ MyBatis ( www.mybatis.org ) ช่วยให้คุณเขียนคำสั่ง SQL นอกโค้ด java ของคุณและแมปผลลัพธ์ sql ลงในวัตถุ java ของคุณเหนือสิ่งอื่นใด


3

Google มีห้องสมุดที่เรียกว่าห้อง Persitence ห้องสมุดซึ่งมีวิธีทำความสะอาดมากของการเขียน SQL สำหรับ Android Appsพื้นชั้น abstraction กว่าต้นแบบฐานข้อมูล SQLite Bellow เป็นข้อมูลโค้ดสั้น ๆ จากเว็บไซต์ทางการ:

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

มีตัวอย่างเพิ่มเติมและเอกสารประกอบที่ดีกว่าในเอกสารอย่างเป็นทางการสำหรับไลบรารี

นอกจากนี้ยังเป็นหนึ่งเรียกว่า MentaBean ซึ่งเป็นJava ออม มีคุณสมบัติที่ดีและดูเหมือนจะเป็นวิธีการเขียน SQL ที่ค่อนข้างง่าย


ตามเอกสารเกี่ยวกับห้อง : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. ดังนั้นจึงไม่ใช่ไลบรารี ORM ทั่วไปสำหรับ RDBMS มีไว้สำหรับแอป Android เป็นหลัก
RafiAlhamd

2

อ่านไฟล์ XML

คุณสามารถอ่านได้จากไฟล์ XML ง่ายต่อการบำรุงรักษาและใช้งานได้ มีตัวแยกวิเคราะห์ STaX, DOM, SAX มาตรฐานที่พร้อมใช้งานเพื่อให้โค้ดไม่กี่บรรทัดใน java

ทำสิ่งต่างๆได้มากขึ้นด้วยแอตทริบิวต์

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

บำรุงรักษา

คุณสามารถใส่ xml นอกโถและดูแลรักษาได้อย่างง่ายดาย ประโยชน์เช่นเดียวกับไฟล์คุณสมบัติ

การแปลง

XML สามารถขยายและแปลงเป็นรูปแบบอื่นได้ง่าย

ใช้กรณี

Metamug ใช้ xml เพื่อกำหนดค่าไฟล์รีซอร์ส REST ด้วย sql


คุณสามารถใช้ yaml หรือ json ได้หากต้องการ ดีกว่าเก็บในไฟล์คุณสมบัติธรรมดา
ตัวเรียงลำดับ

คำถามคือจะสร้าง SQL ได้อย่างไร ในการสร้าง SQL หากคุณจำเป็นต้องใช้ XML, Parser, Validation และอื่น ๆ มันมีภาระมากเกินไป ความพยายามในช่วงแรก ๆ ส่วนใหญ่ที่เกี่ยวข้องกับ XML ในการสร้าง SQL นั้นถูกหันไปใช้ Annotation คำตอบที่ได้รับการยอมรับโดยPiotr Kochanskiเป็นง่ายและสง่างามและตรงประเด็น - แก้ปัญหาและการบำรุงรักษา หมายเหตุ: ไม่มีวิธีอื่นในการรักษา SQL ที่ดีกว่าในภาษาอื่น
RafiAlhamd

ฉันลบความคิดเห็นก่อนหน้านี้I don't see a reason to make use of XML. เนื่องจากไม่สามารถแก้ไขได้
RafiAlhamd

1

หากคุณใส่สตริง SQL ในไฟล์คุณสมบัติแล้วอ่านว่าคุณสามารถเก็บสตริง SQL ไว้ในไฟล์ข้อความธรรมดาได้

นั่นไม่ได้แก้ปัญหาประเภท SQL แต่อย่างน้อยก็ทำให้การคัดลอกและวางจาก TOAD หรือ sqlplus ง่ายขึ้นมาก


0

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

คุณไม่ได้สร้างสตริง SQL โดยตรงใช่หรือไม่? นั่นเป็นข้อห้ามที่ใหญ่ที่สุดในการเขียนโปรแกรม โปรดใช้ PreparedStatements และระบุข้อมูลเป็นพารามิเตอร์ ช่วยลดโอกาสของ SQL Injection ได้อย่างมาก


แต่ถ้าคุณไม่ได้เปิดเผยหน้าเว็บสู่สาธารณะ - SQL Injection เป็นปัญหาที่เกี่ยวข้องหรือไม่?
Vidar

4
SQL Injection มีความเกี่ยวข้องเสมอเนื่องจากอาจเกิดขึ้นโดยบังเอิญและโดยเจตนา
sleske

1
@Vidar - คุณอาจไม่ได้เปิดเผยหน้าเว็บต่อสาธารณะในตอนนี้แต่แม้แต่โค้ดที่ "เสมอ" จะอยู่ภายในก็มักจะได้รับการเปิดเผยจากภายนอกบางอย่างในบางประเด็น และทั้งเร็วกว่าและปลอดภัยกว่าในการทำครั้งแรกในรอบแรกมากกว่าที่จะต้องตรวจสอบโค้ดเบสทั้งหมดเพื่อหาปัญหาในภายหลัง ...
Andrzej Doyle

4
แม้แต่ PreparedStatement ก็ต้องสร้างจาก String ไม่ใช่เหรอ?
Stewart

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