Cara terbersih untuk membangun string SQL di Java

107

Saya ingin membangun string SQL untuk melakukan manipulasi database (memperbarui, menghapus, menyisipkan, memilih, hal semacam itu) - alih-alih metode concat string yang mengerikan menggunakan jutaan "+" dan tanda kutip yang paling tidak dapat dibaca - ada pasti cara yang lebih baik.

Saya memang berpikir untuk menggunakan MessageFormat - tetapi seharusnya digunakan untuk pesan pengguna, meskipun saya pikir itu akan melakukan pekerjaan yang wajar - tapi saya kira harus ada sesuatu yang lebih selaras dengan operasi tipe SQL di perpustakaan java sql.

Apa Groovy bagus?

Vidar
sumber

Jawaban:

76

Pertama-tama pertimbangkan untuk menggunakan parameter kueri dalam pernyataan yang disiapkan:

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

Hal lain yang bisa dilakukan adalah menyimpan semua kueri di file properti. Misalnya dalam file queries.properties dapat menempatkan kueri di atas:

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

Kemudian dengan bantuan kelas utilitas sederhana:

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

}

Anda mungkin menggunakan kueri Anda sebagai berikut:

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

Ini adalah solusi yang agak sederhana, tetapi berfungsi dengan baik.

Piotr Kochański
sumber
1
Saya lebih suka menggunakan SQL builder yang bersih seperti ini: mentabean.soliveirajr.com
TraderJoeChicago
2
Bolehkah saya menyarankan Anda memasukkan bagian InputStreamdalam if (props == null)pernyataan sehingga Anda tidak membuat instance saat tidak diperlukan.
SyntaxRules
64

Untuk SQL sembarang, gunakan jOOQ . jOOQ saat ini mendukung SELECT, INSERT, UPDATE, DELETE, TRUNCATE, dan MERGE. Anda dapat membuat SQL seperti ini:

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

Alih-alih mendapatkan string SQL, Anda juga bisa menjalankannya, menggunakan jOOQ. Lihat

http://www.jooq.org

(Penafian: Saya bekerja untuk perusahaan di belakang jOOQ)

Lukas Eder
sumber
bukankah ini dalam banyak kasus menjadi solusi yang buruk karena Anda tidak dapat membiarkan dbms mengurai pernyataan sebelumnya dengan nilai yang berbeda untuk "5", "8", dll.? Saya kira mengeksekusi dengan jooq akan menyelesaikannya?
Vegard
@Vegard: Anda memiliki kendali penuh atas bagaimana jOOQ harus merender nilai bind dalam output SQL-nya: jooq.org/doc/3.1/manual/sql-building/bind-values . Dengan kata lain, Anda bisa memilih apakah akan merender "?"atau apakah akan memasukkan nilai ikatan sebaris.
Lukas Eder
ya, tapi dalam hal cara bersih untuk membangun sql, ini akan menjadi kode yang agak berantakan di mata saya jika Anda tidak menggunakan JOOQ untuk mengeksekusi. dalam contoh ini Anda menyetel A ke 1, B ke 2 dll, tetapi Anda harus melakukannya sekali lagi saat Anda mengeksekusi jika Anda tidak mengeksekusi dengan JOOQ.
Vegard
1
@Vegard: Tidak ada yang menghalangi Anda untuk meneruskan variabel ke jOOQ API, dan membangun kembali pernyataan SQL. Selain itu, Anda dapat mengekstrak nilai bind dalam urutannya menggunakan jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() , atau menamai nilai bind dengan namanya menggunakan jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Jawaban saya hanya berisi contoh yang sangat sederhana ... Saya tidak yakin apakah ini menjawab kekhawatiran Anda?
Lukas Eder
2
Ini solusi yang mahal.
Penyortir
15

Salah satu teknologi yang harus Anda pertimbangkan adalah SQLJ - cara untuk menyematkan pernyataan SQL langsung di Java. Sebagai contoh sederhana, Anda mungkin memiliki yang berikut ini dalam file bernama TestQueries.sqlj:

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

Ada langkah precompile tambahan yang mengambil file .sqlj Anda dan menerjemahkannya ke dalam Java murni - singkatnya, ini mencari blok khusus yang dipisahkan dengan

#sql
{
    ...
}

dan mengubahnya menjadi panggilan JDBC. Ada beberapa manfaat utama menggunakan SQLJ:

  • sepenuhnya mengabstraksi lapisan JDBC - programmer hanya perlu memikirkan tentang Java dan SQL
  • penerjemah dapat dibuat untuk memeriksa kueri Anda untuk sintaks dll terhadap database pada waktu kompilasi
  • kemampuan untuk langsung mengikat variabel Java dalam kueri menggunakan awalan ":"

Ada implementasi penerjemah di sebagian besar vendor database utama, jadi Anda harus dapat menemukan semua yang Anda butuhkan dengan mudah.

Ashley Mercer
sumber
Yang ini sudah usang sekarang, sesuai wikipedia.
Zeus
1
Pada saat penulisan (Jan 2016) SQLJ disebut di Wikipedia sebagai "usang" tanpa referensi apa pun. Apakah sudah secara resmi ditinggalkan? Jika demikian, saya akan memberikan peringatan di bagian atas jawaban ini.
Ashley Mercer
NB Teknologi tersebut masih didukung misalnya pada versi terbaru Oracle, 12c . Saya akui ini bukan standar paling modern, tetapi masih berfungsi dan memiliki beberapa manfaat (seperti verifikasi waktu kompilasi kueri terhadap DB) yang tidak tersedia di sistem lain.
Ashley Mercer
12

Saya ingin tahu apakah Anda mencari sesuatu seperti Squiggle . Juga sesuatu yang sangat berguna adalah jDBI . Itu tidak akan membantu Anda dengan kueri.

tcurdt
sumber
9

Saya akan melihat-lihat Spring JDBC . Saya menggunakannya setiap kali saya perlu menjalankan SQL secara terprogram. Contoh:

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

Ini sangat bagus untuk segala jenis eksekusi sql, terutama query; ini akan membantu Anda memetakan kumpulan hasil ke objek, tanpa menambahkan kompleksitas ORM lengkap.

Bent André Solheim
sumber
bagaimana saya bisa mendapatkan query sql yang dieksekusi secara nyata? Saya ingin mencatatnya.
kodmanyagha
5

Saya cenderung menggunakan Parameter JDBC Bernama Spring sehingga saya dapat menulis string standar seperti "pilih * dari bla di mana colX = ': someValue'"; Saya pikir itu cukup mudah dibaca.

Alternatifnya adalah memasukkan string dalam file .sql terpisah dan membaca konten menggunakan metode utilitas.

Oh, patut juga untuk melihat Squill: https://squill.dev.java.net/docs/tutorial.html

GaryF
sumber
Saya berasumsi maksud Anda Anda menggunakan BeanPropertySqlParameterSource? Saya hampir setuju dengan Anda, kelas yang baru saja saya sebutkan keren saat menggunakan kacang-kacangan secara ketat tetapi jika tidak, saya akan merekomendasikan menggunakan ParameterizedRowMapper khusus untuk membuat objek.
Esko
Tidak terlalu. Anda dapat menggunakan SqlParameterSource apa pun dengan Named JDBC Parameters. Ini sesuai dengan kebutuhan saya untuk menggunakan MapSqlParameterSource, daripada varietas kacang. Bagaimanapun, ini adalah solusi yang bagus. RowMappers, bagaimanapun, berurusan dengan sisi lain dari teka-teki SQL: mengubah kumpulan hasil menjadi objek.
GaryF
4

Saya mendukung rekomendasi untuk menggunakan ORM seperti Hibernate. Namun, pasti ada situasi di mana itu tidak berhasil, jadi saya akan menggunakan kesempatan ini untuk mempromosikan beberapa hal yang telah saya bantu untuk menulis: SqlBuilder adalah pustaka java untuk secara dinamis membangun pernyataan sql menggunakan gaya "pembangun". itu cukup kuat dan cukup fleksibel.

james
sumber
4

Saya telah mengerjakan aplikasi servlet Java yang perlu membuat pernyataan SQL yang sangat dinamis untuk tujuan pelaporan adhoc. Fungsi dasar dari aplikasi ini adalah untuk memasukkan banyak parameter permintaan HTTP bernama ke dalam kueri yang telah dikodekan sebelumnya, dan menghasilkan tabel keluaran yang diformat dengan baik. Saya menggunakan Spring MVC dan kerangka kerja injeksi ketergantungan untuk menyimpan semua kueri SQL saya dalam file XML dan memuatnya ke dalam aplikasi pelaporan, bersama dengan informasi pemformatan tabel. Akhirnya, persyaratan pelaporan menjadi lebih rumit daripada kemampuan kerangka kerja pemetaan parameter yang ada dan saya harus menulis sendiri. Itu adalah latihan yang menarik dalam pengembangan dan menghasilkan kerangka kerja untuk pemetaan parameter yang jauh lebih kuat daripada apa pun yang dapat saya temukan.

Pemetaan parameter baru terlihat seperti ini:

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

Keindahan dari framework yang dihasilkan adalah ia dapat memproses parameter permintaan HTTP secara langsung ke dalam kueri dengan pemeriksaan jenis dan pemeriksaan batas yang tepat. Tidak ada pemetaan tambahan yang diperlukan untuk validasi input. Dalam contoh kueri di atas, parameter bernama serverId akan diperiksa untuk memastikannya dapat mentransmisikan ke integer dan berada dalam kisaran 0-50. Parameter appId akan diproses sebagai larik bilangan bulat, dengan batas panjang 50. Jika bidang showOwnerada dan disetel ke "true", bit SQL dalam tanda kutip akan ditambahkan ke kueri yang dihasilkan untuk pemetaan bidang opsional. field Beberapa pemetaan tipe parameter lainnya tersedia termasuk segmen opsional SQL dengan pemetaan parameter lebih lanjut. Ini memungkinkan pemetaan kueri serumit yang bisa dihasilkan oleh pengembang. Ia bahkan memiliki kontrol dalam konfigurasi laporan untuk menentukan apakah kueri tertentu akan memiliki pemetaan akhir melalui PreparedStatement atau hanya dijalankan sebagai kueri yang dibuat sebelumnya.

Untuk contoh nilai permintaan Http:

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

Ini akan menghasilkan SQL berikut:

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

Saya benar-benar berpikir bahwa Spring atau Hibernate atau salah satu kerangka kerja tersebut harus menawarkan mekanisme pemetaan yang lebih kuat yang memverifikasi tipe, memungkinkan tipe data kompleks seperti array dan fitur serupa lainnya. Saya menulis mesin saya hanya untuk tujuan saya, tidak cukup dibaca untuk rilis umum. Saat ini hanya berfungsi dengan kueri Oracle dan semua kode itu milik perusahaan besar. Suatu hari nanti saya dapat mengambil ide-ide saya dan membangun kerangka kerja sumber terbuka baru, tetapi saya berharap salah satu pemain besar yang ada akan mengambil tantangan.

Natalia
sumber
3

Mengapa Anda ingin membuat semua sql dengan tangan? Pernahkah Anda melihat ORM seperti Hibernate Tergantung pada proyek Anda, itu mungkin akan melakukan setidaknya 95% dari apa yang Anda butuhkan, melakukannya dengan cara yang lebih bersih daripada SQL mentah, dan jika Anda perlu mendapatkan kinerja terakhir Anda dapat membuat Kueri SQL yang perlu disetel secara manual.

Jared
sumber
3

Anda juga dapat mengunjungi MyBatis ( www.mybatis.org ). Ini membantu Anda menulis pernyataan SQL di luar kode java Anda dan memetakan hasil sql ke dalam objek java Anda antara lain.

joshua
sumber
3

Google menyediakan pustaka yang disebut Pustaka Persitensi Ruangan yang menyediakan cara yang sangat bersih untuk menulis SQL untuk Aplikasi Android , pada dasarnya lapisan abstraksi di atas Basis Data SQLite yang mendasarinya . Di bawah ini adalah potongan kode singkat dari situs resmi:

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

Ada lebih banyak contoh dan dokumentasi yang lebih baik di dokumen resmi untuk perpustakaan.

Ada juga yang disebut MentaBean yang merupakan ORM Java . Ini memiliki fitur bagus dan tampaknya cara yang cukup sederhana untuk menulis SQL.

CasualCoder3
sumber
Sesuai dengan dokumentasi Room : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Jadi, ini bukan pustaka ORM generik untuk RDBMS. Ini terutama dimaksudkan untuk Aplikasi Android.
RafiAlhamd
2

Baca file XML.

Anda dapat membacanya dari file XML. Mudah dirawat dan digunakan. Ada parser STaX, DOM, SAX standar yang tersedia di luar sana untuk membuatnya menjadi beberapa baris kode di java.

Lakukan lebih banyak dengan atribut

Anda dapat memiliki beberapa informasi semantik dengan atribut pada tag untuk membantu melakukan lebih banyak hal dengan SQL. Ini bisa berupa nama metode atau jenis kueri atau apa pun yang membantu Anda mengurangi kode.

Pertahankan

Anda dapat meletakkan xml di luar toples dan merawatnya dengan mudah. Manfaat yang sama seperti file properti.

Konversi

XML dapat diperluas dan mudah diubah ke format lain.

Kasus Penggunaan

Metamug menggunakan xml untuk mengkonfigurasi file resource REST dengan sql.

Tukang sortir
sumber
Anda dapat menggunakan yaml atau json jika Anda menyukainya. Mereka lebih baik daripada menyimpan dalam file properti biasa
Penyortir
Pertanyaannya adalah bagaimana MEMBANGUN SQL. Untuk membangun SQL, jika Anda perlu menggunakan XML, Parser, Validation, dll. Itu membebani. Sebagian besar upaya awal yang melibatkan XML untuk membangun SQL sedang ditolak demi Annotation. The diterima jawaban oleh Piotr Kochański adalah sederhana dan elegan dan to the point - memecahkan masalah dan dipelihara. CATATAN: TIDAK ada cara alternatif untuk mempertahankan SQL yang lebih baik dalam bahasa lain.
RafiAlhamd
Saya menghapus komentar saya sebelumnya I don't see a reason to make use of XML. , karena saya tidak dapat mengeditnya.
RafiAlhamd
1

Jika Anda meletakkan string SQL dalam file properti dan kemudian membacanya di Anda dapat menyimpan string SQL dalam file teks biasa.

Itu tidak menyelesaikan masalah tipe SQL, tapi setidaknya itu membuat menyalin & menempel dari TOAD atau sqlplus jauh lebih mudah.

Rowan
sumber
0

Bagaimana Anda mendapatkan penggabungan string, selain dari string SQL yang panjang di PreparedStatements (yang dapat Anda sediakan dengan mudah dalam file teks dan tetap memuat sebagai sumber daya) yang Anda hancurkan beberapa baris?

Anda tidak membuat string SQL secara langsung, bukan? Itu adalah larangan terbesar dalam pemrograman. Harap gunakan PreparedStatements, dan berikan data sebagai parameter. Ini sangat mengurangi kemungkinan SQL Injection.

JeeBee
sumber
Tetapi jika Anda tidak mengekspos halaman web ke publik - apakah SQL Injection merupakan masalah yang relevan?
Vidar
4
SQL Injection selalu relevan, karena dapat terjadi secara tidak sengaja maupun disengaja.
sleske
1
@Vidar - Anda mungkin tidak akan mengekspos halaman web ke publik sekarang , tetapi bahkan kode yang "selalu" bersifat internal sering kali akhirnya mendapatkan semacam eksposur eksternal beberapa poin lebih jauh. Dan itu lebih cepat dan lebih aman untuk melakukannya dengan benar pada putaran pertama daripada harus mengaudit seluruh basis kode untuk masalah nanti ...
Andrzej Doyle
4
Bahkan PreparedStatement perlu dibuat dari String, bukan?
Stewart
Ya, tetapi aman untuk membuat PreparedStatement dari String, selama Anda membuat PreparedStatement yang aman. Anda mungkin harus menulis kelas PreparedStatementBuilder untuk membuatnya, untuk menyembunyikan kekacauan penggabungan hal-hal.
JeeBee