Java Academy · Oppitunti

PreparedStatement

Turvalliset parametroidut kyselyt.

Oppitunti 2/413 vaihetta

PreparedStatement on ilmainen Java Academy-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Java Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Java Academy-kurssilla on yhteensä 4 oppituntia.

Miksi PreparedStatement?

SQL-kyselyn muodostaminen ketjuttamalla käyttäjän syöte mukaan on vaarallista ja hidasta.

PreparedStatement ratkaisee molemmat ongelmat:

  • Turvallisuus — arvot lähetetään erillään SQL-kyselystä, mikä estää SQL-injektion.
  • Suorituskyky — tietokanta voi jäsentää kyselyn ja laatia sille suoritussuunnitelman kerran ja käyttää sitä sitten uudelleen.

SQL-injektio-ongelma

Ajatelkaa kyselyä, joka muodostetaan merkkijonoja ketjuttamalla. Jos käyttäjä kirjoittaa nimekseen '; DROP TABLE users; --, ketjutettu SQL suorittaa sen komentona.

Tämä on SQL-injektio, yksi vanhimmista ja haitallisimmista verkkosovellusten haavoittuvuuksista. PreparedStatement tekee sen mahdottomaksi rakenteensa ansiosta.

public class Main {
    public static void main(String[] args) {
        String userInput = "'; DROP TABLE users; --";
        // Dangerous: never do this
        String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
        System.out.println("Injected SQL becomes:");
        System.out.println(unsafe);
    }
}

Paikanvaraajat

Kirjoittakaa PreparedStatement-lauseessa jokaisen myöhemmin annettavan arvon kohdalle ?.

Liittäkää arvot sitten tyypitetyillä asetusmetodeilla:

  • ps.setString(1, name)
  • ps.setInt(2, age)

Parametri-indeksit alkavat arvosta 1, ja niiden järjestys vastaa ?-merkkien järjestystä.

Turvallinen kysely

Luokaa lause prepareStatement-metodilla, sitokaa parametrit ja kutsukaa sitten executeQuery-metodia.

Arvo ei koskaan päädy osaksi SQL-tekstiä, joten injektio ei ole mahdollinen käyttäjän syötteestä riippumatta.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "SELECT id FROM users WHERE name = ?";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Ada");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) System.out.println(rs.getInt("id"));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Useiden parametrien sitominen

Jokainen ? saa oman asetusmetodikutsunsa. Järjestyksellä on merkitystä: parametri 1 vastaa ensimmäistä ?-merkkiä, parametri 2 toista ja niin edelleen.

Käyttäkää sarakkeen tyyppiä vastaavaa asetusmetodia — setInt, setString, setDouble, setBoolean ja niin edelleen.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Grace");
            ps.setInt(2, 42);
            ps.setBoolean(3, true);
            int rows = ps.executeUpdate();
            System.out.println("Inserted " + rows + " row");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

NULL-arvon käsittely

SQL:n NULL-arvon sitomiseen käyttäkää setNull(index, type)-metodia ja java.sql.Types-vakiota.

Älkää välittäkö Java-kielen null-arvoa setString-metodille ja olettako sen toimivan — ilmoittakaa tyyppi eksplisiittisesti:

  • ps.setNull(2, java.sql.Types.INTEGER)

Luotujen avainten hakeminen

INSERT-lauseen jälkeen tarvitsette usein automaattisesti luodun tunnisteen.

Välittäkää Statement.RETURN_GENERATED_KEYS valmistelun yhteydessä ja lukekaa sitten tulos ps.getGeneratedKeys()-metodilla.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name) VALUES (?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, "Linus");
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) System.out.println("New id: " + keys.getLong(1));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Eräkäsittely

Tuhansien rivien lisääminen yksi lause kerrallaan on hidasta. Käyttäkää eräkäsittelyä:

  • Sitokaa parametrit ja kutsukaa sitten ps.addBatch()-metodia.
  • Toistakaa tämä jokaiselle riville.
  • Kutsukaa ps.executeBatch()-metodia kerran.

Tämä lähettää rivit yhdessä, mikä vähentää tietokantakierroksia huomattavasti.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO tags(label) VALUES (?)";
        String[] labels = {"java", "jdbc", "sql"};
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (String label : labels) {
                ps.setString(1, label);
                ps.addBatch();
            }
            int[] counts = ps.executeBatch();
            System.out.println("Batched " + counts.length + " inserts");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Lauseen uudelleenkäyttö

Yhtä PreparedStatement-oliota voi suorittaa monta kertaa eri parametreilla.

Sitokaa paikanvaraajat uudelleen ja kutsukaa execute-metodia uudestaan. Tietokanta käyttää välimuistiin tallennettua suoritussuunnitelmaa uudelleen, minkä vuoksi valmistellut lauseet ovat toistuvissa kyselyissä nopeampia.

Älkää koskaan ketjuttako

Kultainen sääntö: kaiken muuttuvan arvon on oltava ?-parametri.

Vain kiinteitä, koodin hallitsemia tunnisteita (kuten sallittujen sarakkeiden nimeä) saa ketjuttaa — silloinkin arvo on tarkistettava sallittujen arvojen luetteloa vasten. Käyttäjän antamat tiedot sidotaan aina parametreiksi, eikä niitä koskaan ketjuteta.

Sulkeminen ja uudelleenkäytön laajuus

PreparedStatement on sidottu Connection-yhteyteensä, ja se tulee sulkea käytön jälkeen.

Ilmoittakaa lause try-with-resources-lohkon sisällä, jotta se suljetaan ennen yhteyttä. Jos käytätte samaa lausetta useita kertoja silmukassa, valmistelkaa se kerran silmukan ulkopuolella ja sitokaa vain parametrit uudelleen silmukan sisällä — valmistelu jokaisella iteraatiolla hukkaa välimuistiin tallennetun suoritussuunnitelman.

Pikatarkistus

Miksi PreparedStatement estää SQL-injektion?

Kertaus

Opitte kirjoittamaan turvallisia ja nopeita kyselyitä:

  • Käyttäkää ?-paikanhaltijoita ja tyyppikohtaisia setter-metodeja (1-pohjainen indeksi).
  • Toimittakaa arvot menetelmillä setString, setInt, setNull ja niin edelleen.
  • Noutakaa automaattisesti luodut avaimet käyttämällä RETURN_GENERATED_KEYS- ja getGeneratedKeys-toimintoja.
  • Suorittakaa useita kirjoitusoperaatioita eränä yhdistämällä addBatch ja executeBatch.
  • Älkää koskaan yhdistäkö käyttäjän syötettä SQL:ään.
Aloita maksutta

Opi Java tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
104
Oppitunnit
374

Usein kysytyt kysymykset

Onko oppitunti ”PreparedStatement” ilmainen?

Kyllä – oppitunnin ”PreparedStatement” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Java Academy-kurssin, päivitä CoddyKit PROhon. Java Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”PreparedStatement”?

Turvalliset parametroidut kyselyt. Harjoittelet Java Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Java Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Java Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.

Kuinka kauan ”PreparedStatement”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Java Academy-oppitunnilla?

Kyllä. Jokainen Java Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. JDBC:n perusteet
  2. PreparedStatement
  3. Transaktiot
  4. Yhteyspooli HikariCP:llä
← Takaisin: Java Academy