Membuka koneksi saja tidak cukup — kita perlu mengeksekusi SQL untuk benar-benar membaca atau mengubah data. JDBC menyediakan tiga method eksekusi di interface Statement, masing-masing cocok untuk situasi berbeda. Memilih method yang tepat membuat kode lebih jelas dan lebih aman. Pada chapter ini kita akan membedah execute(), executeQuery(), dan executeUpdate(), kemudian melihatnya beraksi di SQLite in-memory dengan data pegawai sederhana.
Memahami perbedaan tiga method ini penting karena setiap method mengembalikan tipe yang berbeda dan cocok untuk jenis SQL berbeda. Memakai method yang salah — misalnya executeQuery() untuk INSERT — akan melempar exception di runtime. Sebaliknya, memakai executeUpdate() untuk SELECT juga gagal karena ResultSet tidak dikembalikan. Pemilihan method yang tepat juga membantu pembaca kode memahami nensi operasi tanpa harus membaca SQL-nya.
Tiga Method Eksekusi #
Interface Statement punya tiga method utama untuk mengeksekusi SQL. Pemilihannya didasarkan pada apakah SQL menghasilkan result set (data kembali ke aplikasi) atau hanya mengubah baris (data tetap di database):
| Method | Mengembalikan | Cocok untuk |
|---|---|---|
execute(String sql) | Boolean (true jika ada ResultSet) | SQL yang jenisnya tidak diketahui saat runtime |
executeQuery(String sql) | ResultSet | SELECT yang pasti menghasilkan baris |
executeUpdate(String sql) | Int (jumlah baris terdampak) | INSERT, UPDATE, DELETE, DDL (CREATE, ALTER, DROP) |
executeQuery() lebih nyaman karena mengembalikan ResultSet langsung — tidak perlu cek boolean dan panggil getResultSet() setelahnya. executeUpdate() langsung memberi tahu jumlah baris yang ditambah, diubah, atau dihapus. execute() dipakai saat SQL bisa berupa salah satu dari keduanya — misalnya saat runtime memproses skrip SQL arbitrary.
Baca vs Tulis #
Operasi SQL terbagi dua kategori: read (baca) dan write (tulis). Read adalah SELECT yang tidak mengubah state database; method yang sesuai adalah executeQuery(). Write mencakup INSERT, UPDATE, DELETE, dan DDL; method yang sesuai adalah executeUpdate().
Pemisahan ini bukan sekadar konvensi JDBC — database juga memperlakukan operasi read dan write secara berbeda secara internal. Operasi read bisa paralel di banyak koneksi (dengan snapshot isolation), sementara operasi write biasanya memerlukan locking yang membatasi konkurensi. Database juga bisa mengoptimalkan query read melalui query cache atau materialized view yang berbeda dari jalur tulis.
-- Tulis (DDL)
CREATE TABLE pegawai (id INTEGER PRIMARY KEY, nama TEXT, gaji INTEGER);
-- Tulis (DML INSERT)
INSERT INTO pegawai(nama, gaji) VALUES ('Budi', 5000000);
-- Baca
SELECT nama, gaji FROM pegawai WHERE gaji > 4000000;
-- Tulis (DML UPDATE)
UPDATE pegawai SET gaji = gaji + 500000 WHERE nama = 'Budi';Kode Lengkap: execute, executeQuery, executeUpdate #
Kode berikut membuat tabel pegawai, menyisipkan lima baris, lalu mendemonstrasikan ketiga method eksekusi. Perhatikan bagaimana executeUpdate() mengembalikan jumlah baris, dan executeQuery() mengembalikan ResultSet yang langsung diiterasi.
// executing_sql_queries.kt
// Mendemonstrasikan eksekusi SQL: Statement.execute(), executeQuery(),
// dan executeUpdate(). Memakai SQLite via JDBC.
import java.sql.DriverManager
import java.sql.Connection
fun main() {
Class.forName("org.sqlite.JDBC")
val url = "jdbc:sqlite:file::memory:?cache=shared"
val keeper: Connection = DriverManager.getConnection(url)
DriverManager.getConnection(url).use { conn ->
// Setup: CREATE TABLE (DDL) dan INSERT (DML)
conn.createStatement().use { stmt ->
stmt.execute(
"""CREATE TABLE pegawai (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
gaji INTEGER NOT NULL,
departemen TEXT NOT NULL
)"""
)
stmt.executeUpdate(
"""INSERT INTO pegawai(nama, gaji, departemen) VALUES
('Budi', 5000000, 'Engineering'),
('Siti', 6000000, 'Engineering'),
('Andi', 4500000, 'Marketing'),
('Dewi', 7000000, 'Engineering'),
('Rina', 4000000, 'Marketing')"""
)
}
// 1. execute(): generic, mengembalikan true jika ada ResultSet
println("=== execute() — generic SQL ===")
conn.createStatement().use { stmt ->
val hasResultSet = stmt.execute("SELECT COUNT(*) FROM pegawai")
println("execute() returned hasResultSet=$hasResultSet")
if (hasResultSet) {
stmt.resultSet.use { rs ->
if (rs.next()) println("Total pegawai: ${rs.getInt(1)}")
}
}
}
// 2. executeQuery(): khusus SELECT, mengembalikan ResultSet
println("\n=== executeQuery() — SELECT ===")
conn.createStatement().use { stmt ->
stmt.executeQuery("SELECT nama, gaji FROM pegawai WHERE gaji > 5000000 ORDER BY gaji DESC").use { rs ->
while (rs.next()) {
println(" ${rs.getString("nama")} - Rp${rs.getInt("gaji")}")
}
}
}
// 3. executeUpdate(): khusus INSERT/UPDATE/DELETE/DDL, mengembalikan baris terdampak
println("\n=== executeUpdate() — UPDATE ===")
conn.createStatement().use { stmt ->
val affected = stmt.executeUpdate("UPDATE pegawai SET gaji = gaji + 500000 WHERE departemen = 'Marketing'")
println("Rows affected: $affected")
}
println("\n=== executeUpdate() — DELETE ===")
conn.createStatement().use { stmt ->
val deleted = stmt.executeUpdate("DELETE FROM pegawai WHERE gaji < 4500000")
println("Rows deleted: $deleted")
}
println("\n=== Verifikasi hasil ===")
conn.createStatement().use { stmt ->
stmt.executeQuery("SELECT nama, gaji, departemen FROM pegawai ORDER BY gaji DESC").use { rs ->
while (rs.next()) {
println(" ${rs.getString("nama")} - Rp${rs.getInt("gaji")} (${rs.getString("departemen")})")
}
}
}
println("\n=== Ringkasan ===")
println("| Method | Penggunaan | Return |")
println("|-------------------|--------------------|---------------------------|")
println("| execute() | SQL tidak diketahui| Boolean (hasResultSet) |")
println("| executeQuery() | SELECT | ResultSet |")
println("| executeUpdate() | INSERT/UPDATE/DELETE/DDL | Int (rows affected) |")
println("\n=== Penting: SQL injection ===")
println("Statement.concat user input BERBAHAYA. Gunakan PreparedStatement.")
println(" Aman : conn.prepareStatement(\"WHERE nama = ?\").setString(1, input)")
println(" Bahaya : \"WHERE nama = '\" + input + \"'\" // user input =' OR 1=1 --")
}
keeper.close()
}Output yang Dihasilkan:
=== execute() — generic SQL ===
execute() returned hasResultSet=true
Total pegawai: 5
=== executeQuery() — SELECT ===
Dewi - Rp7000000
Siti - Rp6000000
=== executeUpdate() — UPDATE ===
Rows affected: 2
=== executeUpdate() — DELETE ===
Rows deleted: 0
=== Verifikasi hasil ===
Dewi - Rp7000000 (Engineering)
Siti - Rp6000000 (Engineering)
Budi - Rp5000000 (Engineering)
Andi - Rp5000000 (Marketing)
Rina - Rp4500000 (Marketing)
=== Ringkasan ===
| Method | Penggunaan | Return |
|-------------------|--------------------|---------------------------|
| execute() | SQL tidak diketahui| Boolean (hasResultSet) |
| executeQuery() | SELECT | ResultSet |
| executeUpdate() | INSERT/UPDATE/DELETE/DDL | Int (rows affected) |
=== Penting: SQL injection ===
Statement.concat user input BERBAHAYA. Gunakan PreparedStatement.
Aman : conn.prepareStatement("WHERE nama = ?").setString(1, input)
Bahaya : "WHERE nama = '" + input + "'" // user input =' OR 1=1 --Mari kita telaah kode di atas:
- Baris DDL
CREATE TABLE pegawai: dipakai denganstmt.execute()karena DDL tidak mengembalikanResultSetmaupun jumlah baris yang relevan. Methodexecute()tetap aman untuk DDL. - Baris
INSERT ... VALUES (...), (...): memakaiexecuteUpdate()yang mengembalikan jumlah baris yang ditambah (5 dalam kasus ini). executeQuery("SELECT ..."): mengembalikanResultSetyang langsung diiterasi denganwhile (rs.next()). Kita membaca kolom via nama (rs.getString("nama")) yang lebih jelas daripada index.executeUpdate("UPDATE ... WHERE departemen = 'Marketing'"): mengembalikan jumlah baris yang diubah, yaitu 2 (Andi dan Rina masing-masing naik 500.000).executeUpdate("DELETE FROM pegawai WHERE gaji < 4500000"): menghapus pegawai dengan gaji di bawah 4.500.000.
Referensi Lanjutan #
- java.sql package (Java SE 21)
— API reference
StatementdanexecuteQuerydi paketjava.sql
Selanjutnya #
Kita sudah melihat tiga method eksekusi SQL. Untuk query yang menerima input pengguna, kita membutuhkan cara yang aman dari serangan SQL injection. Chapter Prepared Statements akan memperkenalkan PreparedStatement dan parameter binding.