PreparedStatement
Turvalliset parametroidut kyselyt.
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,setNullja niin edelleen. - Noutakaa automaattisesti luodut avaimet käyttämällä
RETURN_GENERATED_KEYS- jagetGeneratedKeys-toimintoja. - Suorittakaa useita kirjoitusoperaatioita eränä yhdistämällä
addBatchjaexecuteBatch. - Älkää koskaan yhdistäkö käyttäjän syötettä SQL:ään.
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
- JDBC:n perusteet
- PreparedStatement
- Transaktiot
- Yhteyspooli HikariCP:llä