Koneksi yang terbuka masih seperti saluran telepon yang sepi — belum ada satu kalimat SQL pun yang melintas. Objek Statement adalah pembawa pesan pertama kita: dibuat dari Connection, membawa string SQL ke database, lalu membawa pulang hasilnya. JDBC membagi jalur pengiriman menjadi tiga method dengan kontrak tegas: executeQuery untuk SELECT, executeUpdate untuk INSERT/UPDATE/DELETE, dan execute serbaguna yang mengembalikan penanda jenis hasil. Chapter ini membedah ketiganya, lalu memastikan setiap resource ditutup dengan benar karena Statement dan ResultSet sama-sama harus dibersihkan.
Tiga Method Eksekusi dan Kontraknya #
Pemilihan method bukan soal selera — masing-masing punya tipe kembalian yang berbeda karena hasilnya memang berbeda:
| Method | Untuk SQL | Kembalian |
|---|---|---|
executeQuery(sql) | SELECT (dan kueri lain yang menghasilkan baris) | ResultSet |
executeUpdate(sql) | INSERT, UPDATE, DELETE, DDL | int jumlah baris terdampak (0 untuk DDL) |
execute(sql) | bentuk apa pun, misalnya SQL dinamis | boolean: true bila hasilnya ResultSet |
Mekanisme di balik layar berjalan begini: Statement mengirim string SQL mentah ke database, database mem-parse dan mengeksekusinya, lalu driver mengemas balasan. Untuk executeQuery, balasan berupa kumpulan baris yang digulir lewat ResultSet; untuk executeUpdate, database hanya mengirim satu angka — berapa baris yang berubah. Angka itu penting untuk keputusan program: DELETE yang mengembalikan 0 berarti tidak ada data yang cocok dengan kondisi WHERE.
Statement Menjalankan SQL #
Program berikut melanjutkan pola shim dari dua chapter sebelumnya — driver jdbc:demo: menopang tabel in-memory, tetapi seluruh pemanggilan memakai java.sql.Statement sungguhan sehingga kontrak ketiga method terlihat apa adanya. Method execute() kita amati sampai tuntas: hasil true diikuti getResultSet(), hasil false diikuti getUpdateCount():
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EksekusiSql {
public static void main(String[] args) throws SQLException {
DriverManager.registerDriver(new DemoDriver());
try (Connection conn = DriverManager.getConnection("jdbc:demo:toko");
Statement stmt = conn.createStatement()) {
tampilKueri(stmt, "SELECT nama, kota FROM pelanggan");
int n1 = stmt.executeUpdate("INSERT INTO pelanggan VALUES ('Ranto', 'Medan')");
System.out.println("executeUpdate(INSERT) -> " + n1);
int n2 = stmt.executeUpdate(
"UPDATE pelanggan SET kota = 'Depok' WHERE nama = 'Budi'");
System.out.println("executeUpdate(UPDATE) -> " + n2);
int n3 = stmt.executeUpdate("DELETE FROM pelanggan WHERE kota = 'Bandung'");
System.out.println("executeUpdate(DELETE) -> " + n3);
boolean kueri = stmt.execute("SELECT nama, kota FROM pelanggan");
System.out.println("execute(SELECT) -> " + kueri);
try (ResultSet rs = stmt.getResultSet()) {
while (rs.next()) {
System.out.println(" - " + rs.getString("nama")
+ " (" + rs.getString("kota") + ")");
}
}
boolean ubah = stmt.execute("INSERT INTO pelanggan VALUES ('Eka', 'Solo')");
System.out.println("execute(INSERT) -> " + ubah
+ ", getUpdateCount() = " + stmt.getUpdateCount());
tampilKueri(stmt, "SELECT nama, kota FROM pelanggan WHERE kota = 'Depok'");
}
}
static void tampilKueri(Statement stmt, String sql) throws SQLException {
System.out.println("kueri : " + sql);
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
System.out.println(" - " + rs.getString("nama")
+ " (" + rs.getString("kota") + ")");
}
}
}
}
class DemoDriver implements Driver {
@Override
public Connection connect(String url, Properties info) {
if (url == null || !url.startsWith("jdbc:demo:")) {
return null;
}
return ShimFactory.connection();
}
@Override
public boolean acceptsURL(String url) {
return url != null && url.startsWith("jdbc:demo:");
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return 1;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public java.util.logging.Logger getParentLogger() {
return null;
}
}
class ShimFactory {
/** Tabel pelanggan "di database" (in-memory). */
static final List<String[]> TABEL = new ArrayList<>(List.of(
new String[]{"Budi", "Jakarta"},
new String[]{"Siti", "Bandung"},
new String[]{"Dewi", "Surabaya"}));
static Connection connection() {
return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
new Class<?>[]{Connection.class},
(proxy, method, argumen) -> {
if (method.getName().equals("createStatement")) {
return statement();
}
if (method.getName().equals("toString")) {
return "ShimConnection";
}
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
});
}
/** Statement shim dengan state hasil terakhir, seperti Statement sungguhan. */
@SuppressWarnings("unchecked")
static Statement statement() {
List<String[]>[] hasilKini = new List[1]; // hasil SELECT terakhir
int[] jumlahKini = {-1}; // jumlah baris UPDATE/INSERT/DELETE
return (Statement) Proxy.newProxyInstance(
Statement.class.getClassLoader(),
new Class<?>[]{Statement.class},
(proxy, method, argumen) -> {
switch (method.getName()) {
case "executeQuery" -> {
return resultSet(MiniSql.query((String) argumen[0]));
}
case "executeUpdate" -> {
return MiniSql.update((String) argumen[0]);
}
case "execute" -> {
String sql = (String) argumen[0];
if (sql.startsWith("SELECT")) {
hasilKini[0] = MiniSql.query(sql);
jumlahKini[0] = -1;
return true;
}
hasilKini[0] = null;
jumlahKini[0] = MiniSql.update(sql);
return false;
}
case "getResultSet" -> {
return (hasilKini[0] == null)
? null
: resultSet(hasilKini[0]);
}
case "getUpdateCount" -> {
return jumlahKini[0];
}
default -> {
if (method.getName().equals("toString")) {
return "ShimStatement";
}
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
}
}
});
}
/** ResultSet shim: kursor digerakkan di atas salinan hasil kueri. */
static ResultSet resultSet(List<String[]> baris) {
int[] kursor = {-1};
return (ResultSet) Proxy.newProxyInstance(
ResultSet.class.getClassLoader(),
new Class<?>[]{ResultSet.class},
(proxy, method, argumen) -> {
switch (method.getName()) {
case "next" -> {
kursor[0]++;
return kursor[0] < baris.size();
}
case "getString" -> {
int idx = (argumen[0] instanceof Integer angka)
? angka - 1
: ("kota".equals(argumen[0]) ? 1 : 0);
return baris.get(kursor[0])[idx];
}
default -> {
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
}
}
});
}
}
/** Parser SQL mini untuk empat bentuk pernyataan yang dipakai demo. */
class MiniSql {
static int update(String sql) {
Matcher ins = Pattern.compile(
"INSERT INTO pelanggan VALUES \\('([^']+)', '([^']+)'\\)").matcher(sql);
if (ins.matches()) {
ShimFactory.TABEL.add(new String[]{ins.group(1), ins.group(2)});
return 1;
}
Matcher upd = Pattern.compile(
"UPDATE pelanggan SET kota = '([^']+)' WHERE nama = '([^']+)'").matcher(sql);
if (upd.matches()) {
int jumlah = 0;
for (String[] baris : ShimFactory.TABEL) {
if (baris[0].equals(upd.group(2))) {
baris[1] = upd.group(1);
jumlah++;
}
}
return jumlah;
}
Matcher del = Pattern.compile(
"DELETE FROM pelanggan WHERE kota = '([^']+)'").matcher(sql);
if (del.matches()) {
int jumlah = 0;
for (Iterator<String[]> it = ShimFactory.TABEL.iterator(); it.hasNext(); ) {
if (it.next()[1].equals(del.group(1))) {
it.remove();
jumlah++;
}
}
return jumlah;
}
throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
}
static List<String[]> query(String sql) {
Matcher m = Pattern.compile(
"SELECT nama, kota FROM pelanggan(?: WHERE kota = '([^']+)')?")
.matcher(sql);
if (!m.matches()) {
throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
}
List<String[]> hasil = new ArrayList<>();
for (String[] baris : ShimFactory.TABEL) {
if (m.group(1) == null || baris[1].equals(m.group(1))) {
hasil.add(baris);
}
}
return hasil;
}
}Output yang Dihasilkan:
kueri : SELECT nama, kota FROM pelanggan
- Budi (Jakarta)
- Siti (Bandung)
- Dewi (Surabaya)
executeUpdate(INSERT) -> 1
executeUpdate(UPDATE) -> 1
executeUpdate(DELETE) -> 1
execute(SELECT) -> true
- Budi (Depok)
- Dewi (Surabaya)
- Ranto (Medan)
execute(INSERT) -> false, getUpdateCount() = 1
kueri : SELECT nama, kota FROM pelanggan WHERE kota = 'Depok'
- Budi (Depok)Mari kita telaah kontrak yang terbukti pada output di atas:
stmt.executeUpdate("INSERT ...")mengembalikan1: satu baris baru masuk, database melaporkan jumlah baris yang ditambahkan.UPDATE ... WHERE nama = 'Budi'juga mengembalikan1karena tepat satu baris memenuhi kondisi; jika kondisi tidak menangkap satu baris pun, kembalian berupa0.DELETE ... WHERE kota = 'Bandung'menghapus Siti: baris kota Bandung berikutnya tidak lagi muncul pada kueri setelahnya — jumlah1konsisten dengan hilangnya satu baris dari hasilexecute(SELECT).stmt.execute("SELECT ...")mengembalikantrue: penanda bahwa hasil berupaResultSet, yang kemudian diambil lewatstmt.getResultSet(). Pada pemanggilan berikutnya,execute("INSERT ...")mengembalikanfalsedangetUpdateCount()menyajikan1— pola fetch hasil secara eksplisit seperti ini dibutuhkan ketika jenis SQL baru diketahui saat runtime.ShimFactory.statement()menyimpan statehasilKinidanjumlahKini: dua array berukuran satu meniru perilakuStatementasli — hasil eksekusi terakhir tersimpan sampai pemanggilanexecuteberikutnya menimpanya, dangetResultSet()/getUpdateCount()membaca state tersebut.MiniSql.updatedanMiniSql.query: parser regex mini memilih aksi berdasarkan kata kunci SQL lalu mengubah tabel in-memory — pengganti database engine, bukan pengganti kontrak JDBC.
Menutup Resource dengan Benar #
Setiap Statement dan ResultSet memegang resource di sisi database: cursor, buffer jaringan, kadang kunci halaman. Karena keduanya mengimplementasikan AutoCloseable, try-with-resources menutupnya otomatis dalam urutan kebalikan deklarasi — ResultSet lebih dulu, lalu Statement, lalu Connection. Menutup Statement juga otomatis menutup ResultSet yang lahir darinya, tetapi mengandalkan efek berantai tersebut membuat kode sulit dibaca; tutuplah secara eksplisit seperti pada program di atas.
Pada database sungguhan, pola lengkapnya dengan SQLite tampak seperti ini (ditampilkan sebagai contoh karena driver tidak terpasang di lingkungan uji kita):
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:toko.db");
Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT nama, kota FROM pelanggan")) {
while (rs.next()) {
System.out.println(rs.getString("nama") + " | " + rs.getString("kota"));
}
}
int berubah = stmt.executeUpdate(
"UPDATE pelanggan SET kota = 'Depok' WHERE nama = 'Budi'");
System.out.println("Baris terdampak: " + berubah);
}executeQuery mengembalikan ResultSet yang kita gulir dengan rs.next() — tetapi perpindahan kursor, pembacaan kolom berdasarkan label atau indeks, dan penanganan NULL punya aturan main sendiri. Sebelum melompat jauh ke sana, kita tangani dulu kerabat Statement yang lebih penting: PreparedStatement dengan parameter terikat.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
EksekusiSql.java
Unduh berkasimport java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EksekusiSql {
public static void main(String[] args) throws SQLException {
DriverManager.registerDriver(new DemoDriver());
try (Connection conn = DriverManager.getConnection("jdbc:demo:toko");
Statement stmt = conn.createStatement()) {
tampilKueri(stmt, "SELECT nama, kota FROM pelanggan");
int n1 = stmt.executeUpdate("INSERT INTO pelanggan VALUES ('Ranto', 'Medan')");
System.out.println("executeUpdate(INSERT) -> " + n1);
int n2 = stmt.executeUpdate(
"UPDATE pelanggan SET kota = 'Depok' WHERE nama = 'Budi'");
System.out.println("executeUpdate(UPDATE) -> " + n2);
int n3 = stmt.executeUpdate("DELETE FROM pelanggan WHERE kota = 'Bandung'");
System.out.println("executeUpdate(DELETE) -> " + n3);
boolean kueri = stmt.execute("SELECT nama, kota FROM pelanggan");
System.out.println("execute(SELECT) -> " + kueri);
try (ResultSet rs = stmt.getResultSet()) {
while (rs.next()) {
System.out.println(" - " + rs.getString("nama")
+ " (" + rs.getString("kota") + ")");
}
}
boolean ubah = stmt.execute("INSERT INTO pelanggan VALUES ('Eka', 'Solo')");
System.out.println("execute(INSERT) -> " + ubah
+ ", getUpdateCount() = " + stmt.getUpdateCount());
tampilKueri(stmt, "SELECT nama, kota FROM pelanggan WHERE kota = 'Depok'");
}
}
static void tampilKueri(Statement stmt, String sql) throws SQLException {
System.out.println("kueri : " + sql);
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
System.out.println(" - " + rs.getString("nama")
+ " (" + rs.getString("kota") + ")");
}
}
}
}
class DemoDriver implements Driver {
@Override
public Connection connect(String url, Properties info) {
if (url == null || !url.startsWith("jdbc:demo:")) {
return null;
}
return ShimFactory.connection();
}
@Override
public boolean acceptsURL(String url) {
return url != null && url.startsWith("jdbc:demo:");
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return 1;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public java.util.logging.Logger getParentLogger() {
return null;
}
}
class ShimFactory {
/** Tabel pelanggan "di database" (in-memory). */
static final List<String[]> TABEL = new ArrayList<>(List.of(
new String[]{"Budi", "Jakarta"},
new String[]{"Siti", "Bandung"},
new String[]{"Dewi", "Surabaya"}));
static Connection connection() {
return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
new Class<?>[]{Connection.class},
(proxy, method, argumen) -> {
if (method.getName().equals("createStatement")) {
return statement();
}
if (method.getName().equals("toString")) {
return "ShimConnection";
}
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
});
}
/** Statement shim dengan state hasil terakhir, seperti Statement sungguhan. */
@SuppressWarnings("unchecked")
static Statement statement() {
List<String[]>[] hasilKini = new List[1]; // hasil SELECT terakhir
int[] jumlahKini = {-1}; // jumlah baris UPDATE/INSERT/DELETE
return (Statement) Proxy.newProxyInstance(
Statement.class.getClassLoader(),
new Class<?>[]{Statement.class},
(proxy, method, argumen) -> {
switch (method.getName()) {
case "executeQuery" -> {
return resultSet(MiniSql.query((String) argumen[0]));
}
case "executeUpdate" -> {
return MiniSql.update((String) argumen[0]);
}
case "execute" -> {
String sql = (String) argumen[0];
if (sql.startsWith("SELECT")) {
hasilKini[0] = MiniSql.query(sql);
jumlahKini[0] = -1;
return true;
}
hasilKini[0] = null;
jumlahKini[0] = MiniSql.update(sql);
return false;
}
case "getResultSet" -> {
return (hasilKini[0] == null)
? null
: resultSet(hasilKini[0]);
}
case "getUpdateCount" -> {
return jumlahKini[0];
}
default -> {
if (method.getName().equals("toString")) {
return "ShimStatement";
}
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
}
}
});
}
/** ResultSet shim: kursor digerakkan di atas salinan hasil kueri. */
static ResultSet resultSet(List<String[]> baris) {
int[] kursor = {-1};
return (ResultSet) Proxy.newProxyInstance(
ResultSet.class.getClassLoader(),
new Class<?>[]{ResultSet.class},
(proxy, method, argumen) -> {
switch (method.getName()) {
case "next" -> {
kursor[0]++;
return kursor[0] < baris.size();
}
case "getString" -> {
int idx = (argumen[0] instanceof Integer angka)
? angka - 1
: ("kota".equals(argumen[0]) ? 1 : 0);
return baris.get(kursor[0])[idx];
}
default -> {
Class<?> t = method.getReturnType();
if (t == boolean.class) return false;
if (t == int.class) return 0;
return null;
}
}
});
}
}
/** Parser SQL mini untuk empat bentuk pernyataan yang dipakai demo. */
class MiniSql {
static int update(String sql) {
Matcher ins = Pattern.compile(
"INSERT INTO pelanggan VALUES \\('([^']+)', '([^']+)'\\)").matcher(sql);
if (ins.matches()) {
ShimFactory.TABEL.add(new String[]{ins.group(1), ins.group(2)});
return 1;
}
Matcher upd = Pattern.compile(
"UPDATE pelanggan SET kota = '([^']+)' WHERE nama = '([^']+)'").matcher(sql);
if (upd.matches()) {
int jumlah = 0;
for (String[] baris : ShimFactory.TABEL) {
if (baris[0].equals(upd.group(2))) {
baris[1] = upd.group(1);
jumlah++;
}
}
return jumlah;
}
Matcher del = Pattern.compile(
"DELETE FROM pelanggan WHERE kota = '([^']+)'").matcher(sql);
if (del.matches()) {
int jumlah = 0;
for (Iterator<String[]> it = ShimFactory.TABEL.iterator(); it.hasNext(); ) {
if (it.next()[1].equals(del.group(1))) {
it.remove();
jumlah++;
}
}
return jumlah;
}
throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
}
static List<String[]> query(String sql) {
Matcher m = Pattern.compile(
"SELECT nama, kota FROM pelanggan(?: WHERE kota = '([^']+)')?")
.matcher(sql);
if (!m.matches()) {
throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
}
List<String[]> hasil = new ArrayList<>();
for (String[] baris : ShimFactory.TABEL) {
if (m.group(1) == null || baris[1].equals(m.group(1))) {
hasil.add(baris);
}
}
return hasil;
}
}