Məzmuna keç
Educora
İrəli23 dəq16 / 18

JDBC və verilənlər bazaları

Java proqramını JDBC ilə verilənlər bazasına qoş: `PreparedStatement`, SQL inyeksiyasından qorunma, tranzaksiyalar, bağlantı hovuzları və JPA/Hibernate-ə giriş.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • JDBC ilə bazaya qoşulmaq, sorğu göndərmək və ResultSet-i oxumaq
  • PreparedStatement ilə SQL inyeksiyasının qarşısını almaq
  • Tranzaksiyaları idarə etmək, bağlantı hovuzunun və JPA-nın rolunu izah etmək

Proqram bağlananda yaddaşdakı bütün obyektlər silinir. Şagirdlərin qiymətləri, sifarişlər, istifadəçi hesabları isə illərlə qorunmalıdır — onlar verilənlər bazasında saxlanılır. SQL kursunda sorğuları əl ilə yazırdın; indi Java proqramının özü bazaya qoşulacaq, SQL göndərəcək və nəticəni obyektlərə çevirəcək. Bunun əsası JDBC-dir.

Tərif
JDBC

Java Database Connectivity — java.sql paketindəki standart API. Kod Connection, PreparedStatement, ResultSet interfeysləri ilə yazılır, konkret bazanın (PostgreSQL, MySQL, SQLite, H2) drayveri isə onları reallaşdırır. Bazanı dəyişəndə adətən yalnız drayver və qoşulma sətri dəyişir.

Qoşulma və ilk sorğu

Drayver layihəyə adi asılılıq kimi əlavə olunur (Maven-də adətən runtime scope ilə). DriverManager.getConnection(url, user, password) bağlantı açır; URL bazanın növünü və ünvanını göstərir. Statement-in üç əsas metodu var: execute istənilən SQL-i icra edir, executeUpdate dəyişən sətirlərin sayını qaytarır, executeQuery isə nəticə cədvəlini — ResultSet qaytarır. rs.next() kursoru növbəti sətrə keçirir və sətirlər bitəndə false verir. Bütün resursları try-with-resources ilə bağlamaq vacibdir.

BazaURL nümunəsiDrayver (Maven)
PostgreSQLjdbc:postgresql://localhost:5432/schoolorg.postgresql:postgresql
MySQLjdbc:mysql://localhost:3306/schoolcom.mysql:mysql-connector-j
SQLitejdbc:sqlite:school.dborg.xerial:sqlite-jdbc
H2 (yaddaşda)jdbc:h2:mem:schoolcom.h2database:h2
Java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) throws SQLException {
        String url = "jdbc:h2:mem:school";   // the H2 driver must be on the classpath
        try (Connection conn = DriverManager.getConnection(url, "sa", "");
             Statement st = conn.createStatement()) {
            st.execute("CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, "
                    + "first_name VARCHAR(50), city VARCHAR(50), score INT)");
            int rows = st.executeUpdate("INSERT INTO students (first_name, city, score) VALUES "
                    + "('Aysel', 'Baku', 95), ('Murad', 'Ganja', 78), ('Leyla', 'Baku', 88)");
            System.out.println("Inserted rows: " + rows);

            try (ResultSet rs = st.executeQuery("SELECT first_name, score FROM students ORDER BY score DESC")) {
                while (rs.next()) {
                    System.out.println(rs.getString("first_name") + " " + rs.getInt("score"));
                }
            }
        }
    }
}
Gözlənilən nəticə
Inserted rows: 3
Aysel 95
Leyla 88
Murad 78
Nümunə nəticə: H2 drayveri ilə işə salınıb (drayver olmasa, proqram No suitable driver found xətası verir)

PreparedStatement və SQL inyeksiyası

İstifadəçidən gələn qiyməti SQL sətrinə birləşdirmək ən təhlükəli səhvlərdəndir. Əgər kimsə giriş sahəsinə ' OR '1'='1 yazsa, şərt həmişə doğru olur və sorğu bütün istifadəçiləri qaytarır — bu, SQL inyeksiyasıdır. PreparedStatement isə SQL-i ? yer tutanları ilə əvvəlcədən hazırlayır, qiymətləri ayrıca göndərir: baza onları heç vaxt əmr kimi deyil, həmişə verilən kimi qəbul edir. Üstəlik, eyni hazırlanmış sorğu təkrar işlədiləndə daha sürətli ola bilər.

Təhlükəli: sətir birləşdirmə
String sql = "SELECT * FROM users WHERE login = '" + login + "'";
ResultSet rs = st.executeQuery(sql);
// login = ' OR '1'='1   ->   WHERE login = '' OR '1'='1'   ->   every user!
Təhlükəsiz: `?` parametrləri
String sql = "SELECT * FROM users WHERE login = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, login);   // sent separately, always treated as data
ResultSet rs = ps.executeQuery();
Java
static List<String> findByCity(Connection conn, String city, int minScore) throws SQLException {
    String sql = "SELECT first_name FROM students WHERE city = ? AND score >= ? ORDER BY first_name";
    List<String> names = new ArrayList<>();
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setString(1, city);        // parameters are numbered from 1
        ps.setInt(2, minScore);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                names.add(rs.getString("first_name"));
            }
        }
    }
    return names;
}
// findByCity(conn, "Baku", 80)  ->  [Aysel, Leyla]
Tipik oxuma metodu: parametrlər, ResultSet və nəticəni siyahıya yığmaq

Real proqramda hər sətir adətən obyektə çevrilir: new Student(rs.getLong("id"), rs.getString("first_name"), rs.getInt("score")). Bir cədvəl üçün bütün oxuma və yazma metodlarını bir sinifdə toplayırlar — ona DAO (Data Access Object) və ya repozitori deyilir. Beləliklə, SQL bir yerdə qalır, proqramın qalan hissəsi isə yalnız Student obyektləri ilə işləyir və bazanın hansı olduğunu bilmir.

Tranzaksiyalar

Aysel Muradın hesabına 50 manat köçürür: pul bir hesabdan çıxmalı, digərinə daxil olmalıdır. Birinci UPDATE-dən sonra server sönsə, pul «itər». Tranzaksiya bir neçə əməliyyatı bölünməz vahidə çevirir: ya hamısı yadda saxlanır (commit), ya heç biri (rollback). JDBC-də bağlantı susmaya görə auto-commit rejimindədir — hər sorğu dərhal saxlanılır. setAutoCommit(false) bu rejimi söndürür və tranzaksiyanı əl ilə idarə etməyə imkan verir.

Java
static void transfer(Connection conn, int fromId, int toId, int amount) throws SQLException {
    String sql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
    conn.setAutoCommit(false);                    // start a transaction
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setInt(1, -amount);
        ps.setInt(2, fromId);
        ps.executeUpdate();
        ps.setInt(1, amount);
        ps.setInt(2, toId);
        ps.executeUpdate();
        conn.commit();                            // both changes are saved together...
    } catch (SQLException e) {
        conn.rollback();                          // ...or neither of them
        throw e;
    } finally {
        conn.setAutoCommit(true);
    }
}
Hər iki UPDATE ya birlikdə uğurlu olur, ya da birlikdə ləğv edilir

Bağlantı hovuzu

Yeni bağlantı açmaq bahalıdır: şəbəkə, parol yoxlaması, baza tərəfində yaddaş. Bunu hər sorğu üçün təkrarlayan veb-tətbiq yük altında çökür. Bağlantı hovuzu (connection pool) əvvəlcədən bir neçə bağlantı açır və onları «borc verir»: getConnection() hazır bağlantını götürür, close() isə onu bağlamır, hovuza qaytarır. Java-da ən məşhur hovuz HikariCP-dir; Spring Boot ondan susmaya görə istifadə edir.

Java
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/school");
config.setUsername("school_app");
config.setPassword(System.getenv("DB_PASSWORD"));
config.setMaximumPoolSize(10);

try (HikariDataSource pool = new HikariDataSource(config)) {    // create once per application
    try (Connection conn = pool.getConnection()) {              // borrowed from the pool
        System.out.println(findByCity(conn, "Baku", 80));
    }                                                           // close() returns it to the pool
}
HikariCP (com.zaxxer:HikariCP); parolu koda yazma — onu mühit dəyişənindən oxu

JPA və Hibernate: SQL-siz obyektlər

JDBC ilə hər sütunu əl ilə obyektə köçürmək lazımdır. ORM (Object-Relational Mapping) bu işi avtomatlaşdırır: sinif cədvələ, sahə sütuna, obyekt isə sətrə uyğunlaşdırılır. Java-da ORM standartı JPA-dır (Jakarta Persistence, jakarta.persistence paketi), ən populyar reallaşdırması isə Hibernate. Sinfi @Entity ilə işarələyirsən, @Id əsas açarı, @Column isə sütunun adını və məhdudiyyətlərini göstərir.

Java
import jakarta.persistence.*;

@Entity
@Table(name = "students")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "first_name", nullable = false)
    private String firstName;
    private String city;
    private int score;

    protected Student() {}                        // JPA needs a no-argument constructor

    public Student(String firstName, String city, int score) {
        this.firstName = firstName;
        this.city = city;
        this.score = score;
    }

    public String getFirstName() { return firstName; }
    public int getScore() { return score; }
}
Cədvəlin Java sinfi kimi təsviri
Java
EntityManager em = emf.createEntityManager();

em.getTransaction().begin();
em.persist(new Student("Nigar", "Sumgait", 91));        // Hibernate generates the INSERT
em.getTransaction().commit();

Student first = em.find(Student.class, 1L);             // SELECT ... WHERE id = 1

List<Student> best = em.createQuery(
        "SELECT s FROM Student s WHERE s.score >= :min ORDER BY s.score DESC", Student.class)
    .setParameter("min", 90)
    .getResultList();
EntityManager SQL-i özü yaradır; sorğular JPQL dilində sinif və sahə adları ilə yazılır

JPQL-də students cədvəli yox, Student sinfi, first_name sütunu yox, firstName sahəsi yazılır. Növbəti dərsdə görəcəksən ki, Spring Data JPA bu kodu da qısaldır: interface StudentRepository extends JpaRepository<Student, Long> yazmaq kifayətdir, save, findById, findAll metodlarını isə Spring özü yaradır. Amma ORM-in altında yenə JDBC işləyir, ona görə JDBC-ni bilmək problemləri anlamağa kömək edir.

Əsas fikirlər

  • JDBC: Connection → PreparedStatement → ResultSet; drayver URL-ə görə seçilir, resurslar try-with-resources ilə bağlanır.
  • İstifadəçi qiymətini heç vaxt SQL-ə birləşdirmə; 1-dən nömrələnən ? parametrlərindən istifadə et.
  • executeQuery ResultSet qaytarır, executeUpdate isə dəyişən sətirlərin sayını; rs.next() kursoru irəli aparır.
  • Tranzaksiya: setAutoCommit(false) → commit() və ya rollback() — ya hamısı, ya heç biri.
  • Bağlantı hovuzu (HikariCP) bağlantıları təkrar istifadə edir; JPA/Hibernate @Entity və @Id ilə sinifləri cədvəllərə bağlayır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
executeUpdate nə qaytarır?