- JDBC ilə bazaya qoşulmaq, sorğu göndərmək və
ResultSet-i oxumaq PreparedStatementilə 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.
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.
| Baza | URL nümunəsi | Drayver (Maven) |
|---|---|---|
| PostgreSQL | jdbc:postgresql://localhost:5432/school | org.postgresql:postgresql |
| MySQL | jdbc:mysql://localhost:3306/school | com.mysql:mysql-connector-j |
| SQLite | jdbc:sqlite:school.db | org.xerial:sqlite-jdbc |
| H2 (yaddaşda) | jdbc:h2:mem:school | com.h2database:h2 |
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"));
}
}
}
}
}Inserted rows: 3 Aysel 95 Leyla 88 Murad 78
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.
String sql = "SELECT * FROM users WHERE login = '" + login + "'";
ResultSet rs = st.executeQuery(sql);
// login = ' OR '1'='1 -> WHERE login = '' OR '1'='1' -> every user!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();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]ResultSet və nəticəni siyahıya yığmaqReal 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.
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);
}
}UPDATE ya birlikdə uğurlu olur, ya da birlikdə ləğv edilirBağ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.
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
}com.zaxxer:HikariCP); parolu koda yazma — onu mühit dəyişənindən oxuJPA 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.
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; }
}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ırJPQL-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. executeQueryResultSetqaytarır,executeUpdateisə dəyişən sətirlərin sayını;rs.next()kursoru irəli aparır.- Tranzaksiya:
setAutoCommit(false)→commit()və yarollback()— ya hamısı, ya heç biri. - Bağlantı hovuzu (HikariCP) bağlantıları təkrar istifadə edir; JPA/Hibernate
@Entityvə@Idilə sinifləri cədvəllərə bağlayır.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
executeUpdate nə qaytarır?