Ось готовий урок, створений у стилі CS50, де ми розбираємо складну тему ORM (Object-Relational Mapping) просто, енергійно та з практичною користю.
🎓 Тема уроку: Моделі та мапінг таблиць (ORM)
(Або: Як перестати писати SQL у рядках і почати жити)
1. 🔥 Вступ: проблема та мотивація
Уявіть собі ситуацію. Ви пишете код на Python (або Java, C#, JS — неважливо). У вас є об'єкт — скажімо, User (користувач). У нього є ім'я, email, вік. Це все живе в оперативній пам'яті.
Але ж дані треба десь зберігати, правда? Ви відкриваєте базу даних (наприклад, PostgreSQL або MySQL), і там у вас є таблиця.
І тут починається біль.
Щоб зберегти користувача, ви змушені робити щось таке:
# Виглядає страшно, правда?
sql_query = f"INSERT INTO users (name, email, age) VALUES ('{user.name}', '{user.email}', {user.age})"
cursor.execute(sql_query)
Я поставлю вам питання: Ви відчуваєте, наскільки це неприродно? Ви пишете код однією мовою, а всередині нього — рядок іншою мовою (SQL).
- А що, як ви забули закрити лапку в SQL?
- А що, як хтось введе в ім'я символ
'і зламає ваш запит (SQL Injection)? - А якщо ви перейменуєте колонку в базі, вам доведеться шукати цей текст по всьому проєкту?
Це як намагатися керувати автомобілем, вигукуючи команди механіку, який сидить під капотом, замість того, щоб просто крутити кермо.
Сьогодні ми це змінимо. Ми навчимося використовувати ORM (Object-Relational Mapping). Це технологія, яка дозволяє працювати з базою даних так, ніби це звичайні об'єкти у вашому коді. Більше ніяких SQL-рядків. Тільки чистий код.
2. 🧠 Теоретична база (без сухої академічності)
Що таке "Мапінг" (Mapping)? Це просто карта відповідності. Уявіть словник перекладу.
В світі Коду (ООП) у нас є Класи та Об'єкти. В світі Баз Даних (SQL) у нас є Таблиці та Рядки.
ORM — це перекладач, який каже: 1. Цей Клас = Ця Таблиця. 2. Цей Атрибут класу = Ця Колонка. 3. Цей Екземпляр об'єкта = Цей Рядок у таблиці.
Як це працює "під капотом"?
Коли ви кажете коду: "Збережи цього користувача", ORM-бібліотека (наприклад, SQLAlchemy для Python або Hibernate для Java) тихесенько генерує за вас правильний INSERT запит і відправляє його в базу. Коли ви кажете "Дай мені всіх користувачів з іменем Давид", вона генерує SELECT.
📌 Що треба запам'ятати (Залізне правило):
- Модель — це клас у коді, який описує таблицю.
- Ми не працюємо з колонками вручну, ми змінюємо властивості об'єкта.
Інтуїтивно: Ви просто працюєте з об'єктами, а база даних — це просто "місце", куди вони магічним чином зберігаються.
3. 🧪 Приклади (від простого до реального)
Давайте подивимось на це в коді (використаємо псевдокод у стилі Python/SQLAlchemy, бо це стандарт індустрії).
Приклад 1: Визначаємо Модель
Чого ми очікуємо? Ми хочемо створити таблицю users, але не хочемо писати CREATE TABLE.
class User(Model):
# Це назва таблиці в базі
__tablename__ = 'users'
# Це колонки
id = Column(Integer, primary_key=True)
username = Column(String)
email = Column(String)
is_active = Column(Boolean, default=True)
Що сталося? Ми просто описали клас. А ORM, побачивши це, піде в базу і створить таблицю з колонками id, username, email та is_active.
Приклад 2: Додавання даних (Insert)
Чого ми очікуємо? Ми хочемо додати нового студента, не пишучи INSERT INTO....
# Створюємо об'єкт (як звичайну змінну!)
new_student = User(username="David", email="malan@harvard.edu")
# Магічна команда збереження
session.add(new_student)
session.commit()
Результат: У базі даних з'явився новий рядок. Ви не написали жодного слова на SQL. Чисто, красиво, безпечно.
Приклад 3: Пошук та зміна (Select & Update)
Задача: Знайти користувача "David" і змінити йому email.
# 1. Знаходимо (SELECT * FROM users WHERE username = 'David')
user = session.query(User).filter_by(username="David").first()
# 2. Змінюємо (просто міняємо атрибут об'єкта!)
user.email = "david@cs50.harvard.edu"
# 3. Фіксуємо зміни (UPDATE users SET email = ... WHERE id = ...)
session.commit()
Пояснення: Ви бачите? Ви працюєте з user так, ніби це просто змінна в пам'яті. ORM відслідковує зміни ("брудні" дані) і сама формує запит UPDATE тільки для тих полів, що змінилися.
4. 🛠 Практична частина
Час закачати рукави. Уявіть, що ми робимо бекенд для онлайн-магазину піци 🍕.
Завдання 1: Створення моделі
Напиши клас-модель Pizza. У неї мають бути поля:
* id (число)
* name (назва піци, рядок)
* price (ціна, число з плаваючою крапкою)
* size (розмір, рядок, наприклад 'L', 'M', 'S')
Завдання 2: "Пекти піцу"
Напиши код, який створює об'єкт піци "Pepperoni" ціною 150.00 грн і зберігає його в базу (використовуй session.add).
Завдання 3: Інфляція
Напиши запит, який знаходить піцу за назвою "Pepperoni" і піднімає їй ціну на 20 гривень. Не забудь commit().
Завдання 4: Міні-кейс
Прийшов клієнт і каже: "Я хочу бачити тільки дешеві піци".
Напиши запит (використовуючи методи ORM, наприклад .filter()), який поверне список всіх піц, ціна яких менша за 200 грн.
Завдання 5: А що, якщо...?
Що станеться, якщо ми спробуємо зберегти піцу без назви, а в базі даних стоїть обмеження NOT NULL? Як ORM про це повідомить? (Спробуй здогадатися про тип помилки).
5. 💡 Мислення як у розробника
Ви тепер знаєте магію. Але, як і з будь-якою магією, тут є ціна.
⚠️ Типова помилка новачка: "N+1 проблема"
Уявіть, що ви завантажили список із 100 користувачів. Потім ви біжите по цьому списку циклом for і для кожного користувача просите завантажити його адресу.
ORM слухняно зробить... 101 запит до бази даних (1 для списку + 100 для адрес). Це вб'є вашу продуктивність.
Як думає профі: "Я маю сказати ORM завантажити користувачів РАЗОМ з адресами за один раз (Eager loading)".
⚠️ Помилка: Забутий commit()
Ви змінили дані, об'єкт в пам'яті оновився, скрипт завершився... а в базі пусто. Як думає профі: "Транзакція — це святе. Відкрив, змінив, закомітив."
Порада від Девіда:
ORM — це інструмент для швидкості розробки. Вона пише SQL за вас. Але іноді вона пише поганий SQL. Тому хороший розробник завжди іноді заглядає "під капот" (у логи), щоб перевірити, які саме запити летять у базу.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили? 1. Ми викинули "сирі" SQL-рядки з нашого коду. 2. Ми навчилися думати Моделями, а не Таблицями. 3. Ми зрозуміли, що Мапінг — це міст між світом об'єктів і світом даних.
Що ви тепер вмієте? Ви можете спроєктувати базу даних, описавши її звичайними класами, і маніпулювати даними, використовуючи методи вашої мови програмування, а не SQL.
Тизер наступного уроку:
Сьогодні ми говорили про одну таблицю. Але в реальному житті дані пов'язані. У Користувача є Замовлення. У Замовлення є Товари. Як пов'язати ці моделі між собою, не збожеволівши з JOIN? Наступного разу: Відносини (Relationships) та Зовнішні ключі в ORM.
А поки що — це був CS50! 👋