پایتون و نگاشت های شیء-رابطه ای (ORM) — هر آن چه باید در این مورد بدانید
احتمالاً تاکنون چیزهایی در مورد نگاشت شیء-رابطهای (ORM) شنیدهاید. حتی ممکن است از این نگاشتها استفاده کرده باشید؛ اما واقعاً ORM چیست؟ چگونه میتوان از آن در پایتون استفاده کرد؟ در این نوشته هر آن چه که در مورد ORM و پایتون لازم است بدانید را ارائه کردهایم.
ORM چیست؟
نگاشت شیء-رابطهای یا ORM یک تکنیک برنامهنویسی است که برای دسترسی به پایگاه داده مورد استفاده قرار میگیرد. در این تکنیک پایگاه داده در معرض یک سری اشیا قرار میگیرد. بدین ترتیب دیگر نیاز نیست که دستورات SQL برای درج یا بازیابی دادهها نوشته شود و میتوان از یک سری خصوصیات و متدهای متصل به اشیا استفاده نمود.
این تکنیک ممکن است پیچیده و غیرضروری تلقی شود؛ اما میتواند صرفهجویی زمانی زیادی ایجاد کرده و به کنترل دسترسی به پایگاه داده کمک کند.
در ادامه مثالهایی برای استفاده از این تکنیک ارائه شده است. فرض کنید هر بار که میخواهید یک رمز عبور را در پایگاه داده درج کنید، لازم است که آن را هَش (Hash) نمایید. این حالت در استفادههای معمولی مشکلی محسوب نمیشود، کافی است قبل از درج رمز عبور این محاسبات را انجام دهید. اما اگر قرار باشد یک رکورد را در جاهای زیادی درون کد قرار دهید چه باید بکنید؟ اگر برنامهنویس دیگری در جدول شما مطالبی درج کند و شما در مورد آن اطلاعاتی نداشته باشید، چه باید بکنید؟
با استفاده از ORM میتوانید کدی بنویسید که مطمئن شوید هر زمان و هر کجا، هر ردیف یا ستونی در پایگاه داده مورد دسترسی قرار گرفت، ابتدا کد خاص دیگری که نوشتهاید اجرا شود.
این حالت به نام «یگانه منبع اعتماد» (single source of truth) نیز نامیده میشود. اگر بخواهید یک محاسبه خاص را تغییر دهید، کافی است تنها آن را در یک جا تغییر دهید و نه چند جای مختلف. همچنین امکان اجرای بسیاری از این مفاهیم با برنامهنویسی شیءگرا در پایتون وجود دارد؛ اما ORM به همراه مفاهیم شیءگرایی برای کنترل دسترسی به پایگاه داده مورد استفاده قرار میگیرد.
زمانی که میخواهید از نگاشتهای ORM استفاده کنید، چند نکته هستند که باید در نظر داشته باشید. همچنین شرایطی وجود دارند که ممکن است نخواهید از ORM استفاده کنید؛ اما این نگاشتها به طور کلی چیز خوبی هستند و به طور خاص در مورد پایگاههای داده بزرگ، بسیار مفید محسوب میشوند.
نگاشتهای ORM در پایتون با استفاده از SQLAlchemy
همانند اغلب کارها در پایتون، گزینه سریعتر و راحتتر این است که یک ماژول را ایمپورت کنید تا این که خودتان کدی را بنویسید. البته میتوانید خودتان یک ORM بنویسید؛ اما آیا به اختراع مجدد چرخ علاقه دارید؟
مثالهای زیر همگی از SQLAlchemy استفاده میکنند که یک ORM رایج در پایتون است؛ اما بسیاری از مفاهیمی که استفاده شدهاند کلیتر هستند و ربطی به یک پیادهسازی خاص ندارند.
تنظیم پایتون برای SQLAlchemy
پیش از آغاز کار ابتدا میبایست رایانه خود را برای توسعه پایتون به همراه SQLAlchemy آماده کنید.
برای استفاده از مثالهای این نوشته باید پایتون 3.6 را نصب کنید. با این که نسخههای دیگر نیز شبیه هستند؛ اما کدهای زیر برای اجرا در نسخههای قبلیتر به برخی تغییرات نیاز خواهند داشت.
پیش از کدنویسی میبایست محیط پایتون را آماده کنید. بدین ترتیب از بروز مشکلاتی در بستههای ایمپورت شده دیگر پایتون جلوگیری میکنید. مطمئن شوید که PIP یعنی نرمافزار مدیریت بستههای پایتون نصب شده است. در نسخههای جدیدتر پایتون این ابزار به همراه آن ارائه میشود.
زمانی که آماده شدید میتوانید با راهاندازی SQLAlchemy کار خود را آغاز کنید. از درون خط فرمان درون محیط پایتون میتوان SQLAlchemy را با استفاده از دستور pip install به صورت زیر نصب کرد:
pip install SQLAlchemy-1.2.9
نسخه این ماژول 1.2.9 است. اگر میخواهید از جدیدترین نسخه این بسته استفاده کنید، عدد نسخه را حذف کنید؛ اما اشاره به عدد نسخه رویه خوبی در برنامهنویسی به حساب میآید، چون ممکن است نسخه جدیدی از یک بسته موجب از کار افتادن کد شما شود.
اینک آماده کدنویسی هستید. احتمالاً ممکن است نیاز داشته باشید تا پایگاه داده خود را برای پذیرش اتصال از سوی پایتون آماده کنید؛ اما در همه نمونههای زیر از پایگاه داده SQLite استفاده شده است که به صورت درون حافظهای ایجاد میشود.
مدلها در SQLAlchemy
یکی از مؤلفههای اصلی ORM، مدل (model) است. مدل یک کلاس پایتون است که مشخص میکند یک جدول میبایست به چه شکل باشد و چگونه باید عمل کند. در واقع این کلاس نسخه ORM از دستور CREATE TABLE در SQL است. برای هر جدول در پایگاه داده به یک مدل نیاز داریم.
ویرایشگر متنی یا IDE محبوب خود را باز کنید و فایل جدیدی به نام test.py ایجاد کنید. کد آغازین زیر را در آن وارد، فایل را ذخیره کرده و آن را اجرا کنید:
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() engine = create_engine('sqlite://') # Create the database in memory Base.metadata.create_all(engine) # Create all the tables in the database
این کد چند کار انجام میدهد. ابتدا یک ایمپورت داریم که به وسیله آن پایتون درک میکند کجا میتواند ماژولهای SQLAlchemy را بیابد. مدلهای بعدی شما از declarative_base استفاده میکنند که تعیین میکند مدلهای جدید چگونه باید مطابق انتظار شما عمل کنند.
متد create_engine یک اتصال جدید به پایگاه داده ایجاد میکند. اگر از قبل پایگاه دادهای داشته باشید، باید //:sqlite را به URI پایگاه داده خود تغییر دهید. این کد در وضعیتی که هم اینک هست یک پایگاه داده را صرفاً در حافظه موقت ایجاد میکند. این پایگاه داده زمانی که اجرای کد پایان یافت، نابود میشود.
در نهایت متد create_all همه جدولهای تعریف شده در مدلهای شما را در این پایگاه داده ایجاد میکند. از آنجا که هنوز مدلی را تعریف نکردهاید، هیچ اتفاقی نخواهد افتاد. کد را اجرا کنید تا مطمئن شوید که هیچ مشکل یا غلط املایی ندارید.
در ادامه یک مدل میسازیم. دستور ایمپورت دیگری را به ابتدای فایل اضافه کنید:
from sqlalchemy import Column, Integer, String
این دستور ماژولهای Column, Integer و String را از SQLAlchemy ایمپورت میکند. این ماژولها طرز کار جداول، فیلدها، ستونها و انواع دادهی پایگاه داده را تعیین میکنند.
زیر declarative_base کلاس مدل خود را اعلان میکنیم:
class Cars(Base): __tablename__ = 'cars' id = Column(Integer, primary_key=True) make = Column(String(50), nullable=False) color = Column(String(50), nullable=False)
این نمونه ساده از خودروها استفاده میکند؛ اما جدول شما میتواند شامل هرگونه اطلاعاتی باشد.
هر کلاس باید از Base به ارث رسیده باشد. نام جدول پایگاه داده شما در بخش __tablename__ تعیین میشود. این نام باید همان نام کلاس باشد؛ اما این تنها یک توصیه است و در صورتی که بدین ترتیب عمل نکنید هم هیچ مشکلی ایجاد نمیشود.
در نهایت هر ستون به صورت یک متغیر پایتون درون یک کلاس تعریف میشود. انواع دادههای مختلف مورد استفاده قرار میگیرند و خصوصیت primary_key به SQLAlchemy میگوید که ستون id را به صورت کلید اصلی (Primary key) ایجاد کند.
به کار خود ادامه میدهیم و ایمپورت آخر را اضافه میکنیم. این بار ماژول ForeignKey را وارد میکنیم. این ماژول در کنار ایمپورت Column وارد میشود:
from sqlalchemy import Column, ForeignKey, Integer, String
اینک یک کلاس مدل ثانویه نیز ایجاد میکنیم. این کلاس دوم CarOwners نام دارد و اطلاعات مالک خودروهای ذخیره شده در جدول Cars در آن نگهداری خواهد شد.
class CarOwners(Base): __tablename__ = 'carowners' id = Column(Integer, primary_key=True) name = Column(String(50), nullable=False) age = Column(Integer, nullable=False) car_id = Column(Integer, ForeignKey('cars.id')) car = relationship(Cars)
چندین خصوصیت جدید وجود دارند که در اینجا معرفی شدهاند. فیلد car_id به صورت Foreign key تعریف شده است. این فیلد به id در جدول cars مرتبط است. به چگونگی استفاده از حروف کوچک در نام جدول به جای حروف بزرگ در نام کلاس توجه کنید.
در نهایت یک خصوصیت برای car به صورت یک relationship تعریف میشود. بدین ترتیب امکان دسترسی مدل به جدول Cars از طریق این متغیر پدید میآید. این مسئله در ادامه بهتر مشخص شده است.
اگر این کد را اجرا کنید، میبینید که هیچ اتفاقی نمیافتد. دلیل این امر آن است که تاکنون کاری که تأثیر به خصوصی داشته باشد از کدمان نخواستهایم.
اشیا در SQLAlchemy
اینک که مدلها همگی ایجاد شدند، میتوانید دسترسی به اشیا را آغاز بکنید و دادهها را نوشته و بخوانید. قرار دادن منطق هر بخش از برنامه در کلاس و فایل مربوط به خود، ایده مناسبی محسوب میشود؛ اما در حال حاضر میتوانیم آنها را در کنار مدلهای خود بنویسیم.
نوشتن دادهها
در این مثال باید برخی دادهها را درون یک پایگاه داده بنویسید تا بتوانید بعدتر آنها را بخوانید. اگر از یک پایگاه داده موجود استفاده میکنید، ممکن است از قبل دادههایی در آن وجود داشته باشد. در هر صورت توضیح شیوه درج دادهها در جدول سودمند است.
ممکن است عادت داشته باشید از دستور INSERT در اسکیوال استفاده نمایید. SQLAlchemy این کار را برای شما انجام میدهد. در ادامه روش درج یک ردیف در مدل Cars را توضیح دادهایم. ابتدا با یک ایمپورت جدید برای sessionmaker آغاز میکنیم:
from sqlalchemy.orm import sessionmaker
این ایمپورت برای ایجاد اشیای session و DBSession ضروری است. این اشیا برای نوشتن و خواندن دادهها استفاده میشوند:
DBSession = sessionmaker(bind=engine) session = DBSession()
سپس این کد را زیر دستور create_all قرار دهید:
car1 = Cars( make="Ford", color="silver" ) session.add(car1) session.commit()
اجازه بدهید کد فوق را بررسی کنیم. متغیر car1 به صورت یک شیء مبتنی بر مدل Cars تعریف شده است. دو متغیر make و color به عنوان پارامترهای آن تعیین شدهاند. در واقع مثل این است که بگوییم «یک خودرو ایجاد کن، اما هنوز آن را درون پایگاه داده قرار نده». این خودرو در حافظه وجود دارد؛ اما همچنان در انتظار نوشته شدن است.
این خودرو را با دستور session.add به سِشِن خود اضافه میکنیم، و سپس آن را با دستور session.commit در پایگاه داده مینویسیم.
اینک یک مالک را اضافه میکنیم:
owner1 = CarOwners( name="Joe", age="99", car_id=(car1.id) ) session.add(owner1) session.commit()
این کد تقریباً همانند درج قبلی برای مدل Cars است. تفاوت اصلی در اینجا آن است که car_id یک کلید خارجی است و از این رو باید یک شناسه ردیف در جدول دیگر داشته باشد. این شناسه یا id از طریق خصوصیت car1.id تعریف میشود.
نیاز نیست که هیچ کوئری روی پایگاه داده اجرا کرده و یا هرگونه id را پیدا کنید، چون SQLAlchemy این کار را برای شما انجام میدهد. ولی میبایست مطمئن شوید که ابتدا دادهها را کامیت (commit) کردهاید.
خواندن دادهها
زمانی که برخی انواع داده را در پایگاه داده نوشتید، میتوانید شروع به خواندن آنها بکنید. در ادامه شیوه اجرای کوئری بر روی جداول Cars و CarOwners آمده است:
result = session.query(Cars).all()
این کار به همین سادگی است. با استفاده از متد query که در session قرار دارد، میتوانید مدل را ذکر کنید و سپس از متد all برای بازیابی همه نتایج استفاده کنید. اگر میدانید که تنها یک نتیجه خواهد بود میتوانید از متد first به صورت زیر استفاده کنید:
result = session.query(Cars).first()
زمانی که مدل را مورد کوئری قرار دارید و نتایج بازگشتی را در یک متغیر ذخیره کردید، میتوانید از طریق یک شیء به دادهها دسترسی داشته باشید:
print(result[0].color)
دستور فوق رنگ «silver» را نمایش میدهد، چون این رکورد در ردیف نخست قرار دارد. میتوانید بر روی شیء result، حلقهای اجرا کرده و نتایج را یکبهیک بررسی کنید.
از آنجا که رابطه را در مدل خود تعریف کردهاید، امکان دسترسی به جداول مرتبط بدون ذکر کلیدواژه join وجود دارد:
result = session.query(CarOwners).all() print(result[0].name) print(result[0].car.color)
این کد به آن جهت کار میکند که مدل شما شامل جزییاتی در مورد ساختار جدول است و خصوصیت car به صورت یک لینک به جدول cars تعریف شده است.
معایب ORM چیست؟
در این راهنما تنها به معرفی مفاهیم مقدماتی پرداختیم؛ اما زمانی که این مفاهیم را به درستی بیاموزید، میتوانید به سادگی وارد موضوعات پیشرفته نیز بشوید. ORM نیز همچون هر تکنیک دیگری معایبی دارد:
- پیش از آنکه هرگونه کوئری را بتوان اجرا کرد، باید مدل خود را بنویسید،
- یک دستور زبان جدید است که باید آموخته شود،
- برای نیازهای ساده ممکن است روش بسیار پیچیدهای تلقی شود،
- برای شروع به کار میبایست طراحی پایگاه داده خوبی داشته باشید.
این مشکلات فینفسه چندان بزرگ نیستند؛ اما مواردی هستند که میبایست در نظر داشت. اگر بر روی یک پایگاه داده از قبل موجود کار نمیکنید، ممکن است این مسائل باعث زحمت شما بشوند.
اگر با مطالعه این راهنما قانع نشدهاید که ORM ابزار مناسبی برای شما است، در این صورت شاید بهتر باشد که مقاله «آشنایی با دستورات مهم SQL» را بخوانید.
اما گر این نوشته مورد توجه قرار گرفته است، احتمالاً به موارد زیر نیز علاقهمند خواهید بود:
- برنامهنویسی شیءگرا چیست؟ — یک توضیح مقدماتی به زبان ساده
- آموزش برنامه نویسی پایتون – مقدماتی
- طراحی و برنامه نویسی وب
- برنامهنویسی شیءگرا در پایتون — یک راهنمای مقدماتی برای مبتدیان
- آموزش تکمیلی برنامه نویسی پایتون
- مجموعه آموزشهای برنامهنویسی
- آموزش Dapper | چگونه از Dapper استفاده کنیم؟ — به زبان ساده
- آموزش SQL Server Management Studio | کامل، رایگان و گام به گام
- مفهوم کلاس در برنامه نویسی — همراه با نمونه مثال عملی
==
ممنون از آموزشتون در همین رابطه یک آموزش درست کردم فکر میکنم تکمیل کننده اموزش خوبتون باشه bestical.rocks/sqlalchemy