5.3. SQL v. ORM¶
Bazy danych są niezbędnym składnikiem większości aplikacji. Poniżej zwięźle pokażemy, w jaki sposób z wykorzystaniem Pythona można je obsługiwać przy użyiu języka SQL, jak i systemów ORM na przykładzie rozwiązania Peewee.
Note
Niniejszy materiał koncentruje się na poglądowym wyeksponowaniu różnic w kodowaniu, komentarz ograniczono do minimum. Dokładne wyjaśnienia poszczególnych instrukcji znajdziesz w materiale SQL oraz Systemy ORM. W tym ostatnim omówiono również ORM SQLAlchemy.
5.3.1. Połączenie z bazą¶
Na początku pliku sqlraw.py
umieszczamy kod, który importuje moduł do obsługi bazy SQLite3
i przygotowuje obiekt kursora, który posłuży nam do wydawania poleceń SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #! /usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
# utworzenie połączenia z bazą przechowywaną w pamięci RAM
con = sqlite3.connect(':memory:')
# dostęp do kolumn przez indeksy i przez nazwy
con.row_factory = sqlite3.Row
# utworzenie obiektu kursora
cur = con.cursor()
|
System ORM Peewee inicjujemy w pliku ormpw.py
tworząc klasę bazową, która zapewni połączenie z bazą:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #! /usr/bin/env python
# -*- coding: utf-8 -*-
import os
from peewee import *
if os.path.exists('test.db'):
os.remove('test.db')
# tworzymy instancję bazy używanej przez modele
baza = SqliteDatabase('test.db') # ':memory:'
# BazaModel to klasa bazowa dla klas Klasa i Uczen, które
# opisują rekordy tabel "klasa" i "uczen" oraz relacje między nimi
class BazaModel(Model):
class Meta:
database = baza
|
Note
Parametr :memory:
powduje utworzenie bazy danych w pamięci operacyjnej,
która istnieje tylko w czasie wykonywania programu. Aby utworzyć trwałą bazę,
zastąp omawiany prametr nazwę pliku, np. test.db
.
5.3.2. Model bazy¶
Dane w bazie zorganizowane są w tabelach, połączonych najczęściej relacjami.
Aby utworzyć tabele klasa
i uczen
powiązane relacją jeden-do-wielu,
musimy wydać następujące polecenia SQL:
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # tworzenie tabel
cur.executescript("""
DROP TABLE IF EXISTS klasa;
CREATE TABLE IF NOT EXISTS klasa (
id INTEGER PRIMARY KEY ASC,
nazwa varchar(250) NOT NULL,
profil varchar(250) DEFAULT ''
);
DROP TABLE IF EXISTS uczen;
CREATE TABLE IF NOT EXISTS uczen (
id INTEGER PRIMARY KEY ASC,
imie varchar(250) NOT NULL,
nazwisko varchar(250) NOT NULL,
klasa_id INTEGER NOT NULL,
FOREIGN KEY(klasa_id) REFERENCES klasa(id)
)""")
|
Wydawanie poleceń SQL-a wymaga koncentracji na poprawności użycia tego języka, systemy ORM izolują nas od takich szczegółów pozwalając skupić się na logice danych. Tworzymy więc klasy opisujące nasze obiekty, tj. klasy i uczniów. Na podstawie Właściwości tych obieków system ORM utworzy odpowiednie pola tabel. Konkretna klasa lub uczeń, czyli instancje klasy, reprezentować będą rekordy w tabelach.
21 22 23 24 25 26 27 28 29 30 31 32 | class Klasa(BazaModel):
nazwa = CharField(null=False)
profil = CharField(default='')
class Uczen(BazaModel):
imie = CharField(null=False)
nazwisko = CharField(null=False)
klasa = ForeignKeyField(Klasa, related_name='uczniowie')
baza.connect() # nawiązujemy połączenie z bazą
baza.create_tables([Klasa, Uczen], True) # tworzymy tabele
|
5.3.2.1. Ćwiczenie 1¶
Utwórz za pomocą tworzonych skryptów bazy w plikach o nazwach sqlraw.db
oraz
peewee.db
. Następnie otwórz te bazy w interpreterze Sqlite i wykonaj
podane niżej polecenia. Porównaj struktury utworzonych tabel.
sqlite> .tables
sqlite> .schema klasa
sqlite> .schema uczen
5.3.3. Wstawianie danych¶
Chcemy wstawić do naszych tabel dane dwóch klas oraz dwóch uczniów. Korzystając z języka SQL użyjemy następujących poleceń:
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | # wstawiamy dane uczniów
cur.execute('INSERT INTO klasa VALUES(NULL, ?, ?);', ('1A', 'matematyczny'))
cur.execute('INSERT INTO klasa VALUES(NULL, ?, ?);', ('1B', 'humanistyczny'))
# wykonujemy zapytanie SQL, które pobierze id klasy "1A" z tabeli "klasa".
cur.execute('SELECT id FROM klasa WHERE nazwa = ?', ('1A',))
klasa_id = cur.fetchone()[0]
# wstawiamy dane uczniów
cur.execute('INSERT INTO uczen VALUES(?,?,?,?)',
(None, 'Tomasz', 'Nowak', klasa_id))
cur.execute('INSERT INTO uczen VALUES(?,?,?,?)',
(None, 'Adam', 'Kowalski', klasa_id))
# zatwierdzamy zmiany w bazie
con.commit()
|
W systemie ORM pracujemy z instancjami inst_klasa
i inst_uczen
. Nadajemy wartości ich
atrybutom i korzystamy z ich metod:
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | # dodajemy dwie klasy, jeżeli tabela jest pusta
if Klasa.select().count() == 0:
inst_klasa = Klasa(nazwa='1A', profil='matematyczny')
inst_klasa.save()
inst_klasa = Klasa(nazwa='1B', profil='humanistyczny')
inst_klasa.save()
# tworzymy instancję klasy Klasa reprezentującą klasę "1A"
inst_klasa = Klasa.select().where(Klasa.nazwa == '1A').get()
# dodajemy uczniów
inst_uczen = Uczen(imie='Tomasz', nazwisko='Nowak', klasa=inst_klasa)
inst_uczen.save()
inst_uczen = Uczen(imie='Adam', nazwisko='Kowalski', klasa=inst_klasa)
inst_uczen.save()
|
5.3.4. Pobieranie danych¶
Pobieranie danych (czyli kwerenda) wymaga polecenia SELECT języka SQL. Aby wyświetlić dane wszystkich uczniów zapisane w bazie użyjemy kodu:
50 51 52 53 54 55 56 57 58 59 60 61 62 | def czytajdane():
"""Funkcja pobiera i wyświetla dane z bazy"""
cur.execute(
"""
SELECT uczen.id,imie,nazwisko,nazwa FROM uczen,klasa
WHERE uczen.klasa_id=klasa.id
""")
uczniowie = cur.fetchall()
for uczen in uczniowie:
print uczen['id'], uczen['imie'], uczen['nazwisko'], uczen['nazwa']
print ""
czytajdane()
|
W systemie ORM korzystamy z metody select()
instancji reprezentującej ucznia.
Dostęp do danych przechowywanych w innych tabelach uzyskujemy dzięki wyrażeniom
typu inst_uczen.klasa.nazwa
, które generuje podzapytanie zwracające obiekt
klasy przypisanej uczniowi.
50 51 52 53 54 55 56 | def czytajdane():
"""Funkcja pobiera i wyświetla dane z bazy"""
for uczen in Uczen.select(): # lub szybsze: Uczen.select().join(Klasa)
print uczen.id, uczen.imie, uczen.nazwisko, uczen.klasa.nazwa
print ""
czytajdane()
|
Tip
Ze względów wydajnościowych pobieranie danych z innych tabel możemy
zasygnalizować już w głównej kwerendzie, używając metody join()
,
np.: Uczen.select().join(Klasa)
.
5.3.5. Modyfikacja i usuwanie danych¶
Edycja danych zapisanych już w bazie to kolejna częsta operacja. Jeżeli chcemy
przepisać ucznia z klasy do klasy, w przypadku czystego SQL-a musimy pobrać
identyfikator ucznia (uczen_id = cur.fetchone()[0]
),
identyfikator klasy (klasa_id = cur.fetchone()[0]
) i użyć ich w klauzuli UPDATE
.
Usuwany rekord z kolei musimy wskazać w klauzuli WHERE
.
64 65 66 67 68 69 70 71 72 73 74 75 76 | # przepisanie ucznia do innej klasy
cur.execute('SELECT id FROM uczen WHERE nazwisko="Nowak"')
uczen_id = cur.fetchone()[0]
cur.execute('SELECT id FROM klasa WHERE nazwa = ?', ('1B',))
klasa_id = cur.fetchone()[0]
cur.execute('UPDATE uczen SET klasa_id=? WHERE id=?', (klasa_id, uczen_id))
czytajdane()
# usunięcie ucznia o identyfikatorze 1
cur.execute('DELETE FROM uczen WHERE id=?', (1,))
czytajdane()
con.close()
|
W systemie ORM tworzymy instancję reprezentującą ucznia i zmieniamy jej właściwości (inst_uczen.klasa = Klasa.select().where(Klasa.nazwa == '1B').get()
). Usuwając dane w przypadku systemu ORM, usuwamy instancję wskazanego obiektu:
58 59 60 61 62 63 64 65 66 67 68 69 | # przepisanie ucznia do innej klasy
inst_uczen = Uczen.select().join(Klasa).where(Uczen.nazwisko == 'Nowak').get()
inst_uczen.klasa = Klasa.select().where(Klasa.nazwa == '1B').get()
inst_uczen.save() # zapisanie zmian w bazie
czytajdane()
# usunięcie ucznia o identyfikatorze 1
inst_uczen = Uczen.select().where(Uczen.id == 1).get()
inst_uczen.delete_instance()
czytajdane()
baza.close()
|
Note
Po wykonaniu wszystkich założonych operacji na danych połączenie z bazą należy
zamknąć, zwalniając w ten sposób zarezerwowane zasoby. W przypadku modułu sqlite3
wywołujemy polecenie con.close()
, w Peewee baza.close()
.
5.3.6. Podsumowanie¶
Bazę danych można obsługiwać za pomocą języka SQL na niskim poziomie. Zyskujemy wtedy na szybkości działania, ale tracimy przejrzystość kodu, łatwość jego przeglądania i rozwijania. O ile w prostych zastosowaniach można to zaakceptować, o tyle w bardziej rozbudowanych projektach używa się systemów ORM, które pozwalają zarządzać danymi nie w formie tabel, pól i rekordów, ale w formie obiektów reprezentujących logicznie spójne dane. Takie podejście lepiej odpowiada obiektowemu wzorcowi projektowania aplikacji.
Dodatkową zaletą systemów ORM, nie do przecenienia, jest większa odporność na błędy i ewentualne ataki na dane w bazie.
Systemy ORM można łatwo integrować z programami desktopowymi i frameworkami przeznaczonymi do tworzenia aplikacji sieciowych. Wśród tych ostatnich znajdziemy również takie, w których system ORM jest podstawowym składnikiem, np. Django.
5.3.7. Zadania dodatkowe¶
- Wykonaj scenariusz aplikacji Quiz ORM, aby zobaczyć przykład wykorzystania systemów ORM w aplikacjach internetowych.
- Wykonaj scenariusz aplikacji internetowej Czat (cz. 1), zbudowanej z wykorzystaniem frameworku Django, korzystającego z własnego modelu ORM.
5.3.8. Źródła¶
Kolejne wersje tworzonych skryptów znajdziesz w katalogu ~/python101/bazy/sqlorm
.
Uruchamiamy je wydając polecenia:
~/python101$ cd bazy/sqlorm
~/python101/bazy/sqlorm$ python sqlraw0x.py
~/python101/bazy/sqlorm$ python ormpw0x.py
- gdzie x jest numerem kolejnej wersji kodu.
Materiały Python 101
udostępniane przez
Centrum Edukacji Obywatelskiej na licencji
Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.
Utworzony: | 2017-09-08 o 19:38 w Sphinx 1.4.5 |
---|---|
Autorzy: | Patrz plik “Autorzy” |