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:

MethodUntuk SQLKembalian
executeQuery(sql)SELECT (dan kueri lain yang menghasilkan baris)ResultSet
executeUpdate(sql)INSERT, UPDATE, DELETE, DDLint jumlah baris terdampak (0 untuk DDL)
execute(sql)bentuk apa pun, misalnya SQL dinamisboolean: 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():

java EksekusiSql.java
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:

text
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:

  1. stmt.executeUpdate("INSERT ...") mengembalikan 1: satu baris baru masuk, database melaporkan jumlah baris yang ditambahkan. UPDATE ... WHERE nama = 'Budi' juga mengembalikan 1 karena tepat satu baris memenuhi kondisi; jika kondisi tidak menangkap satu baris pun, kembalian berupa 0.
  2. DELETE ... WHERE kota = 'Bandung' menghapus Siti: baris kota Bandung berikutnya tidak lagi muncul pada kueri setelahnya — jumlah 1 konsisten dengan hilangnya satu baris dari hasil execute(SELECT).
  3. stmt.execute("SELECT ...") mengembalikan true: penanda bahwa hasil berupa ResultSet, yang kemudian diambil lewat stmt.getResultSet(). Pada pemanggilan berikutnya, execute("INSERT ...") mengembalikan false dan getUpdateCount() menyajikan 1 — pola fetch hasil secara eksplisit seperti ini dibutuhkan ketika jenis SQL baru diketahui saat runtime.
  4. ShimFactory.statement() menyimpan state hasilKini dan jumlahKini: dua array berukuran satu meniru perilaku Statement asli — hasil eksekusi terakhir tersimpan sampai pemanggilan execute berikutnya menimpanya, dan getResultSet()/getUpdateCount() membaca state tersebut.
  5. MiniSql.update dan MiniSql.query: parser regex mini memilih aksi berdasarkan kata kunci SQL lalu mengubah tabel in-memory — pengganti database engine, bukan pengganti kontrak JDBC.

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):

java SELECT dan UPDATE dengan Statement (butuh driver SQLite)
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 berkas
java
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;
    }
}