Xây dựng Ứng dụng Database Backend với SQLAlchemy và Python

Cài đặt Môi trường Làm việc

Để bắt đầu triển khai mô hình đối tượng quan hệ (ORM) cho ứng dụng Python, trước hết cần trang bị các công cụ cần thiết qua trình quản lý gói pip. Dưới đây là lệnh cài đặt thư viện chính:

pip install sqlalchemy

Tùy thuộc vào loại hệ quản trị cơ sở dữ liệu (RDBMS) bạn sử dụng, hãy bổ sung driver tương ứng:

  • PostgreSQL: pip install psycopg2-binary
  • MySQL: pip install mysql-connector-python
  • SQLite: Đã tích hợp sẵn trong thư viện chuẩn, không cần cài thêm.

Cấu Trúc و Khái Niệm Cốt Lõi

Kiến trúc của SQLAlchemy được xây dựng dựa trên các thành phần chính sau:

  • Engine: Lớp xử lý liên lạc trực tiếp với hệ thống dữ liệu, quản lý chuỗi kết nối.
  • Session: Khu vực lưu trữ tạm thời các thao tác giao dịch, kiểm soát trạng thái của object.
  • Model: Các lớp định nghĩa cấu trúc dữ liệu, ánh xạ tương ứng với bảng trong DB.
  • MetaData: Lưu trữ thông tin schema tổng thể của tất cả các bảng đã khai báo.

Thiết lập Kết Nối

Sử dụng hàm create_engine để khởi tạo điểm đến nguồn dữ liệu. Ví dụ dưới đây minh họa cách thiết lập cho SQLite với tùy chọn hiển thị log chi tiết (echo):

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Định nghĩa đường dẫn kết nối
db_url = 'sqlite:///database_instance.db'
db_engine = create_engine(db_url, echo=True)

# Cấu hình Factory để sinh ra các Session
SessionFactory = sessionmaker(bind=db_engine, autocommit=False, autoflush=False)

# Lấy một session instance để dùng
db_session = SessionFactory()

Đối với PostgreSQL hoặc MySQL, cú pháp URI sẽ thay đổi theo dạng protocol://username:password@host:port/dbname.

Định nghĩa Mô Hình Dữ Liệu

Việc định nghĩa schema bằng Python giúp mã hóa linh hoạt hơn so với viết raw SQL. Dưới đây là ví dụ về các entity chứa quan hệ:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base

Base = declarative_base()

class UserProfile(Base):
    __tablename__ = 'profiles'
    
    uid = Column(Integer, primary_key=True, index=True)
    full_name = Column(String(100), nullable=False)
    email_address = Column(String(255), unique=True, index=True)
    
    # Một người có nhiều bài đăng (1:N)
    articles = relationship("BlogPost", back_populates="owner")

class BlogPost(Base):
    __tablename__ = 'posts'
    
    pid = Column(Integer, primary_key=True, index=True)
    headline = Column(String(200), nullable=False)
    body_content = Column(String(2000))
    writer_uid = Column(Integer, ForeignKey('profiles.uid'))
    
    # Nhiều bài viết thuộc về một tác giả (N:1)
    owner = relationship("UserProfile", back_populates="articles")
    
    # Quan hệ N:N qua bảng phụ (Categories)
    categories = relationship("Category", secondary="post_category_map", back_populates="posts")

class Category(Base):
    __tablename__ = 'categories'
    
    cat_id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), unique=True, nullable=False)
    
    posts = relationship("BlogPost", secondary="post_category_map", back_populates="categories")

# Bảng trung gian cho quan hệ N:N
class PostCategoryMap(Base):
    __tablename__ = 'post_category_map'
    
    pid = Column(Integer, ForeignKey('posts.pid'), primary_key=True)
    cid = Column(Integer, ForeignKey('categories.cat_id'), primary_key=True)

Tạo Cấu Trúc Cơ Sở Dữ Liệu

Thao tác này sẽ tự động sinh ra các bảng tương ứng với các lớp Model đã khai báo trong file script hiện tại:

# Thực thi tạo bảng
Base.metadata.create_all(bind=db_engine)

# Trường hợp cần xóa toàn bộ (thận trọng khi dùng)
# Base.metadata.drop_all(bind=db_engine)

Thực Hiện Thao Tác CRUD

Tạo Mới Dữ Liệu

# Thêm đối tượng đơn lẻ
new_member = UserProfile(full_name="Nguyễn Văn A", email_address="a@example.com")
db_session.add(new_member)
db_session.commit()

# Thêm hàng loạt
members_batch = [
    UserProfile(full_name="Bế Thị B", email_address="b@example.com"),
    UserProfile(full_name="Cao Văn C", email_address="c@example.com")
]
db_session.add_all(members_batch)
db_session.commit()

Gọi Truy Vấn (Read)

# Lấy danh sách toàn bộ
all_members = db_session.query(UserProfile).all()

# Chỉ lấy mục đầu tiên
member_first = db_session.query(UserProfile).first()

# Tìm kiếm qua ID
target_member = db_session.get(UserProfile, 1)

Sửa Chữa Dữ Liệu

# Cập nhật từng đối tượng
update_member = db_session.get(UserProfile, 1)
if update_member:
    update_member.full_name = "Nguyễn Văn Mới"
    db_session.commit()

# Cập nhật khối lượng điều kiện
db_session.query(UserProfile).filter(UserProfile.email_address.like("%@example.com")).update({"full_name": "Thành viên mới"})
db_session.commit()

Xóa Dữ Liệu

# Xóa theo id
delete_target = db_session.get(UserProfile, 1)
db_session.delete(delete_target)
db_session.commit()

# Xóa theo bộ lọc
deleted_count = db_session.query(UserProfile).filter(UserProfile.full_name == "Bế Thị B").delete(synchronize_session=False)
db_session.commit()

Kỹ Thuật Truy Vấn Nâng Cao

Lọc và Sắp Xếp

from sqlalchemy import func, or_

# Điều kiện tìm kiếm cụ thể
result_a = db_session.query(UserProfile).filter(UserProfile.full_name == "Nguyễn Văn A").first()

# Từ khóa mở rộng (wildcard)
results_like = db_session.query(UserProfile).filter(UserProfile.full_name.like("Nguyễn%")).all()

# So sánh tập hợp
results_in = db_session.query(UserProfile).filter(UserProfile.full_name.in_(["Nguyễn Văn A", "Bế Thị B"])).all()

# Logic phức hợp (OR)
complex_search = db_session.query(UserProfile).filter(
    or_(UserProfile.full_name == "Nguyễn Văn A", UserProfile.email_address == "a@example.com")
).all()

Hàm Tổng Hợp (Aggregate)

from sqlalchemy import func

# Đếm số lượng bản ghi
total_records = db_session.query(UserProfile).count()

# Tính trung bình
avg_uid = db_session.query(func.avg(UserProfile.uid)).scalar()

# Nhóm dữ liệu và đếm số lượng con (Ví dụ: mỗi người có bao nhiêu bài)
stats = db_session.query(UserProfile.full_name, func.count(BlogPost.pid)).join(BlogPost).group_by(UserProfile.full_name).all()

Thao Tác Kết Nối (Join)

# JOIN INNER
joined_results = db_session.query(UserProfile, BlogPost).join(BlogPost).filter(BlogPost.headline.contains("Python")).all()

# LEFT OUTER JOIN (kể cả bài không có chủ sở hữu xác định)
outer_results = db_session.query(UserProfile, BlogPost).outerjoin(BlogPost).all()

# Quy định điều kiện join thủ công
custom_join = db_session.query(UserProfile, BlogPost).join(BlogPost, UserProfile.uid == BlogPost.writer_uid).all()

Quản Lý Quan Hệ Giữa Các Đối Tượng

SQLAlchemy hỗ trợ quản lý mối quan hệ một cách tự động mà không cần gọi query riêng biệt nếu cấu hình đúng:

author = UserProfile(full_name="Tô Thị T", email_address="t@example.com")
post_entry = BlogPost(headline="Kỳ Viết Thứ Nhất", body_content="Nội dung demo", owner=author)

db_session.add(post_entry)
db_session.commit()

# Truy cập ngược lại từ bài viết sang tác giả
print(f"Bài viết '{post_entry.headline}' do {post_entry.owner.full_name} viết.")

# Duyệt qua danh sách bài viết của một người
for article in author.articles:
    print(f"- {article.headline}")

# Xử lý quan hệ N:N
cat_py = Category(name="Lập Trình")
cat_db = Category(name="Cơ sở dữ liệu")

post_entry.categories.append(cat_py)
post_entry.categories.append(cat_db)
db_session.commit()

Quản Trị Giao Dịch (Transaction)

Việc đảm bảo tính toàn vẹn của dữ liệu đòi hỏi xử lý lỗi và rollback cẩn thận:

try:
    temp_user = UserProfile(full_name="Test User", email_address="temp@test.com")
    db_session.add(temp_user)
    db_session.commit()
except Exception as err:
    db_session.rollback()
    print(f"Lỗi hệ thống: {err}")

Sử dụng context manager để đảm bảo session luôn đóng đúng cách, tránh rò rỉ kết nối:

from contextlib import contextmanager

@contextmanager
def managed_database():
    db_conn = SessionFactory()
    try:
        yield db_conn
        db_conn.commit()
    except Exception:
        db_conn.rollback()
        raise
    finally:
        db_conn.close()

with managed_database() as conn:
    record = UserProfile(full_name="Context User", email_address="ctx@test.com")
    conn.add(record)

Khuyến Nghị Triển Khai

  1. Vòng đời Session: Tạo session mới cho mỗi yêu cầu truy vấn, đảm bảo giải phóng tài nguyên sau khi hoàn tất.
  2. Chờ Tải (Lazy Loading): Lưu ý hiệu năng khi truy cập quan hệ (lazy vs eager loading) để tránh tình trạng N+1 queries.
  3. Cấu Hình Connection Pool: Tối ưu kích thước pool kết nối phù hợp với tải server.
  4. Bảo Vệ Dữ Liệu: Thêm các layer validate ở mức ứng dụng để đảm bảo ràng buộc nghiệp vụ.
  5. Xử lý Ngoại lệ: Bắt mọi ngoại lệ DB và thực hiện rollback ngay lập tức để giữ trạng thái dữ liệu ổn định.

Thẻ: sqlalchemy python-orm database-design backend-engineering PostgreSQL

Đăng vào ngày 24 tháng 8 lúc 04:50