Cách tốt nhất để tạo chuỗi SQL trong Java


107

Tôi muốn xây dựng một chuỗi SQL để thực hiện thao tác cơ sở dữ liệu (cập nhật, xóa, chèn, chọn, đại loại như vậy) - thay vì phương thức nối chuỗi khủng khiếp bằng cách sử dụng hàng triệu dấu "+" và dấu ngoặc kép không thể đọc được - ở đó phải là một cách tốt hơn.

Tôi đã nghĩ đến việc sử dụng MessageFormat - nhưng nó được cho là được sử dụng cho các thông báo của người dùng, mặc dù tôi nghĩ rằng nó sẽ thực hiện một công việc hợp lý - nhưng tôi đoán nên có thứ gì đó phù hợp hơn với các hoạt động kiểu SQL trong thư viện java sql.

Groovy có tốt không?

Câu trả lời:


76

Trước hết, hãy xem xét việc sử dụng các tham số truy vấn trong các câu lệnh đã chuẩn bị:

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

Điều khác có thể được thực hiện là giữ tất cả các truy vấn trong tệp thuộc tính. Ví dụ: trong tệp queries.properties có thể đặt truy vấn trên:

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

Sau đó, với sự trợ giúp của một lớp tiện ích đơn giản:

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);
    }

}

bạn có thể sử dụng các truy vấn của mình như sau:

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

Đây là một giải pháp khá đơn giản, nhưng hoạt động tốt.


1
Tôi thích sử dụng một người thợ xây SQL sạch như thế này: mentabean.soliveirajr.com
TraderJoeChicago

2
Tôi có thể đề nghị bạn đặt InputStreambên trong của if (props == null)câu lệnh để bạn không khởi tạo nó khi không cần thiết.
SyntaxRules

64

Đối với SQL tùy ý, hãy sử dụng jOOQ . jOOQ hiện sự ủng hộ SELECT, INSERT, UPDATE, DELETE, TRUNCATE, và MERGE. Bạn có thể tạo SQL như sau:

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();

Thay vì lấy chuỗi SQL, bạn cũng có thể thực thi nó bằng cách sử dụng jOOQ. Xem

http://www.jooq.org

(Tuyên bố từ chối trách nhiệm: Tôi làm việc cho công ty đằng sau jOOQ)


Trong nhiều trường hợp, điều này không phải là một giải pháp tồi vì bạn không thể để các dbms phân tích cú pháp trước câu lệnh với các giá trị khác nhau cho "5", "8", v.v.? Tôi đoán thực thi với jooq sẽ giải quyết được nó?
Vegard,

@Vegard: Bạn có toàn quyền kiểm soát cách jOOQ nên hiển thị các giá trị ràng buộc trong đầu ra SQL của nó: jooq.org/doc/3.1/manual/sql-building/bind-values . Nói cách khác, bạn có thể chọn hiển thị "?"hoặc xem nội tuyến các giá trị ràng buộc.
Lukas Eder

vâng, nhưng đối với các cách xây dựng sql rõ ràng, đây sẽ là một đoạn mã hơi lộn xộn trong mắt tôi nếu bạn không sử dụng JOOQ để thực thi. trong ví dụ này, bạn đặt A thành 1, B thành 2, v.v., nhưng bạn phải làm điều đó một lần nữa khi bạn thực thi nếu bạn không thực thi với JOOQ.
Vegard

1
@Vegard: Không có gì ngăn bạn chuyển một biến tới API jOOQ và xây dựng lại câu lệnh SQL. Ngoài ra, bạn có thể trích xuất các giá trị ràng buộc theo thứ tự của chúng bằng cách sử dụng jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() hoặc các giá trị ràng buộc được đặt tên theo tên của chúng bằng cách sử dụng jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Câu trả lời của tôi chỉ chứa một ví dụ rất đơn giản ... Tuy nhiên, tôi không chắc liệu điều này có đáp ứng được mối quan tâm của bạn không?
Lukas Eder

2
Đó là một giải pháp tốn kém.
Sắp xếp

15

Một công nghệ bạn nên xem xét là SQLJ - một cách để nhúng các câu lệnh SQL trực tiếp vào Java. Ví dụ đơn giản, bạn có thể có thông tin sau trong tệp có tên TestQueries.sqlj:

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

Có một bước biên dịch trước bổ sung lấy các tệp .sqlj của bạn và chuyển chúng sang Java thuần túy - nói tóm lại, nó tìm kiếm các khối đặc biệt được phân tách bằng

#sql
{
    ...
}

và biến chúng thành các cuộc gọi JDBC. Có một số lợi ích chính khi sử dụng SQLJ:

  • hoàn toàn trừu tượng hóa lớp JDBC - lập trình viên chỉ cần nghĩ về Java và SQL
  • trình biên dịch có thể được thực hiện để kiểm tra các truy vấn của bạn về cú pháp, v.v. dựa trên cơ sở dữ liệu tại thời điểm biên dịch
  • khả năng liên kết trực tiếp các biến Java trong các truy vấn bằng cách sử dụng tiền tố ":"

Hầu hết các nhà cung cấp cơ sở dữ liệu lớn đều có các bản triển khai của trình dịch, vì vậy bạn có thể dễ dàng tìm thấy mọi thứ mình cần.


Cái này hiện đã lỗi thời, theo wikipedia.
Zeus

1
Tại thời điểm viết bài (tháng 1 năm 2016) SQLJ được Wikipedia gọi là "lỗi thời" mà không có bất kỳ tài liệu tham khảo nào. Nó đã chính thức bị bỏ rơi? Nếu vậy, tôi sẽ dán một cảnh báo ở đầu câu trả lời này.
Ashley Mercer

NB Công nghệ này vẫn được hỗ trợ, ví dụ như trong phiên bản mới nhất của Oracle, 12c . Tôi thừa nhận đây không phải là tiêu chuẩn hiện đại nhất, nhưng nó vẫn hoạt động và có một số lợi ích (chẳng hạn như xác minh thời gian biên dịch các truy vấn đối với DB) mà các hệ thống khác không có.
Ashley Mercer

12

Tôi đang tự hỏi liệu bạn có đang theo đuổi một cái gì đó giống như Squiggle . Ngoài ra một thứ rất hữu ích là jDBI . Nó sẽ không giúp bạn với các truy vấn mặc dù.


9

Tôi sẽ xem Spring JDBC . Tôi sử dụng nó bất cứ khi nào tôi cần thực thi SQL theo chương trình. Thí dụ:

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

Nó thực sự tuyệt vời cho bất kỳ loại thực thi sql nào, đặc biệt là truy vấn; nó sẽ giúp bạn ánh xạ các tập kết quả thành các đối tượng mà không làm tăng thêm sự phức tạp của một ORM hoàn chỉnh.


làm thế nào tôi có thể nhận được truy vấn sql được thực thi thực sự? Tôi muốn ghi lại nó.
kodmanyagha

5

Tôi có xu hướng sử dụng Tham số JDBC Được đặt tên của Spring để tôi có thể viết một chuỗi tiêu chuẩn như "select * from blah where colX = ': someValue'"; Tôi nghĩ nó khá dễ đọc.

Một giải pháp thay thế sẽ là cung cấp chuỗi trong một tệp .sql riêng biệt và đọc nội dung bằng một phương thức tiện ích.

Ồ, cũng đáng để xem qua Squill: https://squill.dev.java.net/docs/tutorial.html


Tôi giả sử ý bạn là bạn đang sử dụng BeanPropertySqlParameterSource? Tôi gần như đồng ý với bạn, lớp tôi vừa đề cập rất tuyệt khi sử dụng các bean nghiêm ngặt nhưng nếu không, tôi khuyên bạn nên sử dụng ParameterizedRowMapper tùy chỉnh để xây dựng các đối tượng.
Esko

Không hẳn. Bạn có thể sử dụng bất kỳ SqlParameterSource nào có Tham số JDBC được đặt tên. Nó phù hợp với nhu cầu của tôi là sử dụng MapSqlParameterSource, hơn là giống đậu. Dù bằng cách nào, đó là một giải pháp tốt. RowMappers, tuy nhiên, giải quyết mặt khác của câu đố SQL: biến các tập kết quả thành các đối tượng.
GaryF

4

Tôi thứ hai là các khuyến nghị sử dụng ORM như Hibernate. Tuy nhiên, chắc chắn có những tình huống mà điều đó không hoạt động, vì vậy tôi sẽ nhân cơ hội này để giới thiệu một số nội dung mà tôi đã giúp viết: SqlBuilder là một thư viện java để tạo động các câu lệnh sql bằng cách sử dụng kiểu "người xây dựng". nó khá mạnh mẽ và khá linh hoạt.


4

Tôi đang làm việc trên một ứng dụng Java servlet cần tạo các câu lệnh SQL rất động cho mục đích báo cáo adhoc. Chức năng cơ bản của ứng dụng là cung cấp một loạt các tham số yêu cầu HTTP được đặt tên vào một truy vấn được mã hóa trước và tạo một bảng đầu ra được định dạng độc đáo. Tôi đã sử dụng Spring MVC và khung phụ thuộc để lưu trữ tất cả các truy vấn SQL của mình trong các tệp XML và tải chúng vào ứng dụng báo cáo, cùng với thông tin định dạng bảng. Cuối cùng, các yêu cầu báo cáo trở nên phức tạp hơn so với khả năng của các khuôn khổ ánh xạ tham số hiện có và tôi phải tự viết. Đó là một bài tập thú vị trong quá trình phát triển và tạo ra một khuôn khổ cho việc lập bản đồ tham số mạnh mẽ hơn nhiều so với bất kỳ thứ gì khác mà tôi có thể tìm thấy.

Các ánh xạ tham số mới trông như vậy:

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

Vẻ đẹp của khung kết quả là nó có thể xử lý các tham số yêu cầu HTTP trực tiếp vào truy vấn với kiểm tra kiểu thích hợp và kiểm tra giới hạn. Không cần ánh xạ bổ sung để xác thực đầu vào. Trong truy vấn ví dụ ở trên, tham số có tên serverId sẽ được kiểm tra để đảm bảo rằng nó có thể truyền thành một số nguyên và nằm trong khoảng 0-50. Tham số appId sẽ được xử lý dưới dạng một mảng số nguyên với giới hạn độ dài là 50. Nếu trường showOwnerhiện tại và được đặt thành "true", các bit SQL trong dấu ngoặc kép sẽ được thêm vào truy vấn được tạo cho các ánh xạ trường tùy chọn. trường Có sẵn một số ánh xạ kiểu tham số khác bao gồm các phân đoạn tùy chọn của SQL với các ánh xạ tham số khác. Nó cho phép lập bản đồ truy vấn phức tạp như nhà phát triển có thể đưa ra. Nó thậm chí còn có các điều khiển trong cấu hình báo cáo để xác định xem một truy vấn nhất định sẽ có ánh xạ cuối cùng thông qua PreparedStatement hay chỉ đơn giản là chạy dưới dạng truy vấn được tạo trước.

Đối với các giá trị yêu cầu Http mẫu:

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

Nó sẽ tạo ra SQL sau:

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

Tôi thực sự nghĩ rằng Spring hoặc Hibernate hoặc một trong những khuôn khổ đó nên cung cấp một cơ chế ánh xạ mạnh mẽ hơn để xác minh các loại, cho phép các loại dữ liệu phức tạp như mảng và các tính năng khác như vậy. Tôi đã viết công cụ của mình chỉ cho mục đích của tôi, nó không được đọc cho phát hành chung. Nó chỉ hoạt động với các truy vấn của Oracle vào lúc này và tất cả mã thuộc về một tập đoàn lớn. Một ngày nào đó, tôi có thể lấy ý tưởng của mình và xây dựng một khuôn khổ mã nguồn mở mới, nhưng tôi hy vọng một trong những người chơi lớn hiện tại sẽ chấp nhận thử thách.


3

Tại sao bạn muốn tạo tất cả sql bằng tay? Bạn đã từng xem một ORM như Hibernate Tùy thuộc vào dự án của bạn, nó có thể sẽ làm được ít nhất 95% những gì bạn cần, hãy làm theo cách gọn gàng hơn sau đó là SQL thô và nếu bạn cần có được chút hiệu suất cuối cùng, bạn có thể tạo Các truy vấn SQL cần được điều chỉnh thủ công.


3

Bạn cũng có thể xem qua MyBatis ( www.mybatis.org ). Nó giúp bạn viết các câu lệnh SQL bên ngoài mã java của bạn và ánh xạ các kết quả sql vào các đối tượng java của bạn trong số những thứ khác.


3

Google cung cấp một thư viện được gọi là Thư viện Phối cảnh Phòng cung cấp một cách viết SQL cho Ứng dụng Android rất rõ ràng , về cơ bản là một lớp trừu tượng trên Cơ sở dữ liệu SQLite bên dưới . Dưới đây là đoạn mã ngắn từ trang web chính thức:

@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);
}

Có nhiều ví dụ hơn và tài liệu tốt hơn trong tài liệu chính thức cho thư viện.

Ngoài ra còn có một cái gọi là MentaBean là một ORM Java . Nó có các tính năng hay và có vẻ là cách viết SQL khá đơn giản.


Theo tài liệu Phòng : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Vì vậy, nó không phải là một thư viện ORM chung cho RDBMS. Nó chủ yếu dành cho Ứng dụng Android.
RafiAlhamd

2

Đọc tệp XML.

Bạn có thể đọc nó từ một tệp XML. Dễ dàng bảo trì và làm việc. Có sẵn các trình phân tích cú pháp STaX, DOM, SAX tiêu chuẩn để tạo ra một vài dòng mã trong java.

Làm được nhiều việc hơn với các thuộc tính

Bạn có thể có một số thông tin ngữ nghĩa với các thuộc tính trên thẻ để giúp làm được nhiều việc hơn với SQL. Đây có thể là tên phương thức hoặc loại truy vấn hoặc bất kỳ thứ gì giúp bạn viết mã ít hơn.

Duy trì

Bạn có thể đặt xml bên ngoài lọ và dễ dàng bảo quản. Lợi ích tương tự như một tệp thuộc tính.

Chuyển đổi

XML có thể mở rộng và dễ dàng chuyển đổi sang các định dạng khác.

Trường hợp sử dụng

Metamug sử dụng xml để định cấu hình tệp tài nguyên REST với sql.


Bạn có thể sử dụng yaml hoặc json nếu bạn thích chúng. Chúng tốt hơn là lưu trữ trong một tệp thuộc tính thuần túy
Sorter

Câu hỏi đặt ra là làm thế nào để XÂY DỰNG SQL. Để xây dựng SQL, nếu bạn cần sử dụng XML, Trình phân tích cú pháp, Xác thực, v.v. thì quá tải. Hầu hết các nỗ lực ban đầu liên quan đến XML để xây dựng SQL đều bị từ chối để ủng hộ Annotation. Các câu trả lời được chấp nhận của Piotr Kochańskiđơn giản và thanh lịch và cho điểm - giải quyết vấn đề và duy trì. LƯU Ý: KHÔNG có cách thay thế nào để duy trì một SQL tốt hơn bằng một ngôn ngữ khác.
RafiAlhamd

Tôi đã xóa nhận xét trước đó của mình I don't see a reason to make use of XML. vì tôi không thể chỉnh sửa nó.
RafiAlhamd

1

Nếu bạn đặt các chuỗi SQL trong một tệp thuộc tính và sau đó đọc nó, bạn có thể giữ các chuỗi SQL trong một tệp văn bản thuần túy.

Điều đó không giải quyết được các vấn đề về kiểu SQL, nhưng ít nhất nó làm cho việc sao chép và dán từ TOAD hoặc sqlplus dễ dàng hơn nhiều.


0

Làm cách nào để bạn có thể nối chuỗi, ngoài các chuỗi SQL dài trong PreparedStatements (mà bạn có thể dễ dàng cung cấp trong tệp văn bản và tải dưới dạng tài nguyên) mà bạn ngắt qua một số dòng?

Bạn không tạo chuỗi SQL trực tiếp phải không? Đó là điều cấm kỵ nhất trong lập trình. Vui lòng sử dụng PreparedStatements và cung cấp dữ liệu dưới dạng tham số. Nó làm giảm cơ hội của SQL Injection rất nhiều.


Nhưng nếu bạn không công khai một trang web - SQL Injection có phải là một vấn đề có liên quan không?
Vidar

4
SQL Injection luôn có liên quan, vì nó có thể xảy ra vô tình cũng như có chủ đích.
sleske

1
@Vidar - bạn có thể không hiển thị trang web với công chúng ngay bây giờ , nhưng ngay cả mã "luôn luôn" là nội bộ thường kết thúc bằng một số loại hiển thị bên ngoài ở một số điểm sâu hơn. Và đó là cả hai nhanh hơn và an toàn hơn để làm điều đó vòng đúng thời điểm đầu tiên hơn phải thực hiện kiểm toán toàn bộ codebase cho các vấn đề sau ...
Andrzej Doyle

4
Ngay cả một Câu lệnh Chuẩn bị cũng cần được tạo từ một Chuỗi, phải không?
Stewart

Có, nhưng sẽ an toàn nếu bạn tạo một PreparedStatement từ một chuỗi, miễn là bạn tạo một PreparedStatement an toàn. Bạn có thể nên viết một lớp PreparedStatementBuilder để tạo chúng, nhằm che giấu mớ hỗn độn của việc nối các thứ.
JeeBee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.