Към съдържанието
wannacode

Връзка с базата от код

Накратко: Програмата говори с базата през драйвер: отваря връзка по адреса на базата, подава заявката, чете редовете един по един и затваря връзката.

Проблем

Страницата „Под 5 €“

Сайтът на магазина има страница с евтините продукти. Заявката за нея вече я можеш:

SQLбаза: магазин

Само че на сайта никой не пише заявки в поле. Когато някой отвори страницата, сървърът на магазина пуска програма. Тя праща тази заявка на базата, получава 17-те реда като стойности в своя език (низ, число) и ги слага в HTML. Как точно говори програма с база?

Идеята

Петте стъпки

На всеки език е едно и също:

  1. Драйвер. Библиотека, която знае езика на конкретната база (SQLite, PostgreSQL, MySQL).
  2. Връзка. Програмата казва къде е базата (адрес) и се свързва.
  3. Заявка. Текстът на SQL отива към базата.
  4. Редове. Резултатът идва ред по ред, а програмата чете колоните му като стойности.
  5. Затваряне. Връзката се освобождава, дори ако по средата е станала грешка.

Ето същата заявка, с малко копие на магазина в паметта (:memory:, шест продукта, които setup пуска при старта):

try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
setup(conn);
String sql = "SELECT name, price FROM products WHERE price < 5 ORDER BY price";
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
System.out.printf("%s: %.2f €%n", rs.getString("name"), rs.getDouble("price"));
}
}
}
Изход
Кифла с масло: 0.80 €
Бял хляб: 1.20 €
Прясно мляко: 1.60 €
Масло: 3.20 €

Петата стъпка я прави езикът: try (…) в Java, using в C#, close() в Python и JavaScript. В Python with затваря транзакцията, а не връзката, затова тук close() е изрично.

По-точно, за любопитните

setup пуска SQL текста, който прави таблицата с шестте продукта:

static void setup(Connection conn) throws SQLException {
try (Statement st = conn.createStatement()) {
st.executeUpdate("""
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL, discount INTEGER);
INSERT INTO products VALUES
(1, 'Бял хляб', 1.20, NULL), (3, 'Кифла с масло', 0.80, 10), (5, 'Прясно мляко', 1.60, NULL),
(7, 'Сирене', 7.90, 15), (9, 'Масло', 3.20, NULL), (17, 'Кафе на зърна', 14.90, 0);
""");
}
}

Драйверите в примерите: sqlite-jdbc в Java (пакетът org.xerial:sqlite-jdbc), Microsoft.Data.Sqlite в C# (NuGet), а в Python и JavaScript SQLite е вграден (sqlite3, node:sqlite от Node 22). Всеки от примерите е пуснат и изходът му е точно този.

Виж го

Адресът на базата и паролата

SQLite е файл и адресът е пътят до него. Ако си направил shop.db в урока „SQL и истинските бази“ (sqlite3 shop.db < shop-bg.sql), смени :memory: с пътя и махни setup: на Java е jdbc:sqlite:shop.db, на C# Data Source=shop.db, а на Python и JavaScript просто "shop.db". Същата програма ще покаже всичките 17 продукта.

Сървърна база (PostgreSQL, MySQL, SQL Server) живее в друга програма, често на друг компютър. Адресът има хост, порт, име на базата, потребител и парола:

String url = "jdbc:postgresql://localhost:5432/shop";
String password = System.getenv("SHOP_DB_PASSWORD");
try (Connection conn = DriverManager.getConnection(url, "shop_app", password)) {
// същите заявки
}

Паролата не е в кода. Кодът отива в git и го виждат всички с достъп, а паролата за истинския сайт е различна от тази на твоя компютър. Затова тя идва отвън: от променлива на средата, както тук, или от хранилище за тайни на хостинга.

Тези четири примера не се пускат в проверката на сайта, защото им трябва сървър с PostgreSQL. Драйверите са org.postgresql, Npgsql, psycopg и pg.

Пробвай

Заявката за друга страница

Страницата „Свършили“ в админ панела показва имената на продуктите с наличност 0, по азбучен ред. Напиши заявката, която програмата ще прати.

SQLбаза: магазин

Идеята

От колона до стойност

Всяка колона идва като стойност от типа на езика. INTEGER става int (или long), REAL става double, TEXT става String / string / str. Интересното е NULL. В Python той е None, в JavaScript null, а в Java и C# зависи как четеш:

try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
setup(conn);
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT name, discount FROM products WHERE id IN (1, 3, 17)")) {
while (rs.next()) {
int discount = rs.getInt("discount");
String text = rs.wasNull() ? "без отстъпка" : discount + " %";
System.out.println(rs.getString("name") + ": getInt дава " + discount + ", значи " + text);
}
}
}
Изход
Бял хляб: getInt дава 0, значи без отстъпка
Кифла с масло: getInt дава 10, значи 10 %
Кафе на зърна: getInt дава 0, значи 0 %

Бял хляб е без отстъпка (NULL), а кафето е с отстъпка 0 %. Това са различни неща. В Java getInt дава 0 и за двете и само wasNull() ги различава. В C# GetInt32 върху NULL спира с грешка, затова първо се пита IsDBNull.

Упражнение

Новите продукти

Началната страница показва новите продукти: името и цената на продуктите, добавени (added_at) от 1 юни 2025 нататък, най-новите първи, а при еднаква дата по име.

SQLбаза: магазин

Тест

Провери се

1. Програмата прочете ред с цена и го показа. Кое от тези е стъпка, която се забравя най-често и после спира сайта?
2. Къде е добре да стои паролата за базата на магазина?
3. В Java колоната discount е NULL, а програмата вика rs.getInt("discount"). Какво получава?
4. Заявката е една и съща на четирите езика. Кое се сменя, ако магазинът мине от SQLite на PostgreSQL?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·