1. 程式人生 > >python(十二)下:ORM框架SQLAlchemy使用學習

python(十二)下:ORM框架SQLAlchemy使用學習

func column bar 插入數據 ref min 統計 就是 連接

此出處:http://blog.csdn.net/fgf00/article/details/52949973

本節內容

  1. ORM介紹
  2. sqlalchemy安裝
  3. sqlalchemy基本使用
  4. 多外鍵關聯
  5. 多對多關系
  6. 表結構設計作業
     

一、ORM介紹

如果寫程序用pymysql和程序交互,那是不是要寫原生sql語句。如果進行復雜的查詢,那sql語句就要進行一點一點拼接,而且不太有重用性,擴展不方便。而且寫的sql語句可能不高效,導致程序運行也變慢。
為了避免把sql語句寫死在代碼裏,有沒有一種方法直接把原生sql封裝好了並且以你熟悉的方式操作,像面向對象那樣?
 
orm(object relational mapping),就是對象映射關系程序,簡單來說我們類似python這種面向對象的程序來說一切皆對象,但是我們使用的數據庫卻都是關系型的,為了保證一致的使用習慣,通過orm將編程語言的對象模型和數據庫的關系模型建立映射關系,這樣我們在使用編程語言對數據庫進行操作的時候可以直接使用編程語言的對象模型進行操作就可以了,而不用直接使用sql語言。

ORM 相當於把數據庫也給你實例化了,在代碼操作mysql中級又加了orm這一層。
orm的優點:

  1. 隱藏了數據訪問細節,“封閉”的通用數據庫交互,ORM的核心。他使得我們的通用數據庫交互變得簡單易行,並且完全不用考慮該死的SQL語句。快速開發,由此而來。
  2. ORM使我們構造固化數據結構變得簡單易行。

缺點:

  1. 無可避免的,自動化意味著映射和關聯管理,代價是犧牲性能(早期,這是所有不喜歡ORM人的共同點)。現在的各種ORM框架都在嘗試使用各種方法來減輕這塊(LazyLoad,Cache),效果還是很顯著的。

二、sqlalchemy安裝

在Python中,最有名的ORM框架是SQLAlchemy。用戶包括openstack\Dropbox等知名公司或應用,主要用戶列表http://www.sqlalchemy.org/organizations.html#openstack

安裝sqlalchemy

pip install SQLAlchemy
pip install pymysql
# 由於mysqldb依然不支持py3,所以這裏我們用pymysql與sqlalchemy交互

Centos7 安裝mariadb-server

yum -y install mariadb-server  # 安裝
systemctl start  mariadb.service  # 啟動
mysqladmin -uroot -p password  # 設密碼
GRANT ALL PRIVILEGES ON *.* TO ‘root‘@‘%‘ IDENTIFIED BY PASSWORD ‘123456‘ WITH GRANT OPTION;  # 授權
flush privileges;

三、sqlalchemy基本使用

Dialect用於和數據API進行交流,根據配置文件的不同調用不同的數據庫API,從而實現對數據庫的操作,如:

MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>

pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]

MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>

cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]

1、創建表

import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
# 創建實例,並連接test庫
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘, echo=True)
# echo=True 顯示信息
Base = declarative_base()  # 生成orm基類

class User(Base):
    __tablename__ = ‘user‘  # 表名
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    password = Column(String(64))

Base.metadata.create_all(engine) #創建表結構 (這裏是父類調子類)

運行,顯示相關信息,包括生成的sql語句:

CREATE TABLE user (
    id INTEGER NOT NULL AUTO_INCREMENT, 
    name VARCHAR(32), 
    password VARCHAR(64), 
    PRIMARY KEY (id)
)

並沒有感覺代碼量變少啊, 莫著急,好戲在後面

除上面的創建之外,還有一種創建表的方式

from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey
from sqlalchemy.orm import mapper

metadata = MetaData()

user = Table(‘user‘, metadata,
            Column(‘id‘, Integer, primary_key=True),
            Column(‘name‘, String(50)),
            Column(‘fullname‘, String(50)),
            Column(‘password‘, String(12))
        )

class User(object):
    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

mapper(User, user)  # 類User 和 user關聯起來
# the table metadata is created separately with the Table construct, 
# then associated with the User class via the mapper() function
# 如果數據庫裏有,就不會創建了。

最基本的表我們創建好了,那我們開始用orm創建一條數據試試

2、插入一條數據

from sqlalchemy import create_engine
from sqlalchemy import Table, MetaData, Column, Integer, String
from sqlalchemy.orm import mapper, sessionmaker
# 創建實例,並連接test庫
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘, echo=True)

metadata = MetaData()

user = Table(‘user‘, metadata,
            Column(‘id‘, Integer, primary_key=True),
            Column(‘name‘, String(50)),
            Column(‘password‘, String(12))
        )

class User(object):
    def __init__(self, name, id, password):
        self.id = id
        self.name = name
        self.password = password
# the table metadata is created separately with the Table construct, then associated with the User class via the mapper() function
mapper(User, user)

# 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
Session_class = sessionmaker(bind=engine)  # 實例和engine綁定
Session = Session_class()  # 生成session實例,相當於遊標

user_obj = User(id=27,name="fgf",password="123456")  # 生成你要創建的數據對象
print(user_obj.name,user_obj.id)  # 此時還沒創建對象呢,不信你打印一下id發現還是None

Session.add(user_obj)  # 把要創建的數據對象添加到這個session裏, 一會統一創建
print(user_obj.name,user_obj.id) #此時也依然還沒創建

Session.commit() #現此才統一提交,創建數據

寫這麽多代碼才創建一條數據,你表示太tm的費勁了,正要轉身離開,我拉住你的手不放開,高潮還沒到。。

3.1、查詢

my_user = Session.query(User).filter_by(name="fgf").first()  # 查詢
print(my_user)

此時你看到的輸出是這樣的應該

<__main__.User object at 0x7f0a5a3dea20>

sqlalchemy把返回的數據映射成一個對象啦,這樣你調用每個字段就可以跟調用對象屬性一樣啦,like this..

print(my_user.id,my_user.name,my_user.password)
# 輸出
27 fgf 123456

不過剛才顯示的內存對象對址沒辦法分清返回的是什麽數據的,除非打印具體字段看一下,如果想讓它變的可讀,只需在定義表的類下面加上這樣的代碼

def __repr__(self):
    return "<User(name=‘%s‘,  password=‘%s‘)>" % (
        self.name, self.password)
from sqlalchemy import create_engine
from sqlalchemy import Table, MetaData, Column, Integer, String
from sqlalchemy.orm import mapper, sessionmaker
# 創建實例,並連接test庫
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘, echo=True)

metadata = MetaData()

user = Table(‘user‘, metadata,
            Column(‘id‘, Integer, primary_key=True),
            Column(‘name‘, String(50)),
            Column(‘password‘, String(12))
        )
class User(object):
    def __init__(self, name, id, password):
        self.id = id
        self.name = name
        self.password = password
    def __repr__(self):
        return "<User(name=‘%s‘,  password=‘%s‘)>" % (self.name, self.password)

mapper(User, user)
# 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
Session_class = sessionmaker(bind=engine)
Session = Session_class()  # 生成session實例

my_user = Session.query(User).filter_by(name="fgf").first()  # 查詢第一個
# my_user = Session.query(User).filter_by().all()  # 查詢所有
print(my_user)
# print(my_user.id,my_user.name,my_user.password)

# Session.commit()  #查詢不需要commit

獲取所有數據

print(Session.query(User.name,User.id).all() )

3.2、多條件查詢

filter_by與filter

my_user1 = Session.query(User).filter(User.id>2).all()
my_user2 = Session.query(User).filter_by(id=27).all()  # filter_by相等用‘=’
my_user3 = Session.query(User).filter(User.id==27).all()  # filter相等用‘==’

print(my_user1,‘\n‘,my_user2,‘\n‘,my_user3)

多條件查詢

objs = Session.query(User).filter(User.id>0).filter(User.id<7).all()

上面2個filter的關系相當於 user.id >1 AND user.id <7 的效果

4、修改

my_user = Session.query(User).filter_by(name="fgf").first()
my_user.name = "fenggf"  # 查詢出來之後直接賦值修改
my_user.passwork = "123qwe"
Session.commit()

5、回滾

my_user = Session.query(User).filter_by(id=1).first()
my_user.name = "Jack"

fake_user = User(name=‘Rain‘, password=‘12345‘)
Session.add(fake_user)

print(Session.query(User).filter(User.name.in_([‘Jack‘,‘rain‘])).all() )  #這時看session裏有你剛添加和修改的數據

Session.rollback() #此時你rollback一下

print(Session.query(User).filter(User.name.in_([‘Jack‘,‘rain‘])).all() ) #再查就發現剛才添加的數據沒有了。

# Session
# Session.commit()

6、統計和分組

統計 count

Session.query(User).filter(User.name.like("f%")).count()  # mysql不區分大小寫

分組 group_by

from sqlalchemy import func
print(Session.query(User.name,func.count(User.name)).group_by(User.name).all() )

刪除 del

7.1、外鍵關聯

準備工作:先創建一個表,再插入數據

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
# 創建實例,並連接test庫
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘, echo=True)

Base = declarative_base()  # 生成orm基類

class Student(Base):
    __tablename__ = ‘student‘  # 表名
    id = Column(Integer, primary_key=True, autoincrement=True)
    stu_id = Column(Integer)
    age = Column(Integer)  # 整型
    gender = Column(Enum(‘M‘,‘F‘),nullable=False)

Base.metadata.create_all(engine) #創建表結構 (這裏是父類調子類)

# 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
Session_class = sessionmaker(bind=engine)
Session = Session_class()  # 生成session實例
stu_obj = Student(stu_id=27, age=22, gender="M")
Session.add(stu_obj)
Session.commit() #現此才統一提交,創建數據

連表

ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all()

# 以下兩種 必須表之間有外鍵關聯才能查
ret = session.query(Person).join(Favor).all()  
ret = session.query(Person).join(Favor, isouter=True).all()

第一種示例:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Enum
from sqlalchemy.orm import sessionmaker
# 創建實例,並連接test庫
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘)
Base = declarative_base()  # 生成orm基類

class User(Base):
    __tablename__ = ‘user‘  # 表名
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    password = Column(String(64))
    def __repr__(self):
        return "[%s name:%s]" %(self.id, self.name)

class Student(Base):
    __tablename__ = ‘student‘  # 表名
    id = Column(Integer, primary_key=True, autoincrement=True)
    stu_id = Column(Integer)
    age = Column(Integer)  # 整型
    gender = Column(Enum(‘M‘,‘F‘),nullable=False)
    def __repr__(self):
        return "[%s stu_id:%s sex:%s]" %(self.stu_id, self.age, self.gender)

Session_class = sessionmaker(bind=engine)
Session = Session_class()  # 生成session實例

res = Session.query(User, Student).filter(User.id == Student.stu_id).all()
print(res)

外鍵關聯實現

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Enum,DATE,Integer, String,ForeignKey
from sqlalchemy.orm import sessionmaker,relationship

engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘)
Base = declarative_base()  # 生成orm基類

class Stu2(Base):
    __tablename__ = "stu2"
    id = Column(Integer, primary_key=True)
    name = Column(String(32),nullable=False)
    register_date = Column(DATE,nullable=False)
    def __repr__(self):
        return "<%s name:%s>" % (self.id, self.name)

class StudyRecord(Base):
    __tablename__ = "study_record"
    id = Column(Integer, primary_key=True)
    day = Column(Integer,nullable=False)
    status = Column(String(32),nullable=False)
    stu_id = Column(Integer,ForeignKey("stu2.id"))  #------外鍵關聯------
    #這個nb,允許你在user表裏通過backref字段反向查出所有它在stu2表裏的關聯項數據
    stu2 = relationship("Stu2", backref="my_study_record")  # 添加關系,反查(在內存裏)
    def __repr__(self):
        return "<%s day:%s status:%s>" % (self.stu2.name, self.day,self.status)

Base.metadata.create_all(engine)  # 創建表結構

Session_class = sessionmaker(bind=engine)  # 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
session = Session_class()  # 生成session實例 #cursor

s1 = Stu2(name="A",register_date="2014-05-21")
s2 = Stu2(name="J",register_date="2014-03-21")
s3 = Stu2(name="R",register_date="2014-02-21")
s4 = Stu2(name="E",register_date="2013-01-21")

study_obj1 = StudyRecord(day=1,status="YES", stu_id=1)
study_obj2 = StudyRecord(day=2,status="NO", stu_id=1)
study_obj3 = StudyRecord(day=3,status="YES", stu_id=1)
study_obj4 = StudyRecord(day=1,status="YES", stu_id=2)

session.add_all([s1,s2,s3,s4,study_obj1,study_obj2,study_obj3,study_obj4])  # 創建
session.commit()

stu_obj = session.query(Stu2).filter(Stu2.name=="a").first()  # 查詢
# 在stu2表,查到StudyRecord表的記錄
print(stu_obj.my_study_record)  # 查詢A一共上了幾節課

可以查看創建命令:

show create table study_record;

7.2、多外鍵關聯

多外鍵關聯,並且關聯同一個表。
下表中,Customer表有2個字段都關聯了Address表
cat orm_many_fk.py

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

Base = declarative_base()

class Customer(Base):
    __tablename__ = ‘customer‘
    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    # 賬單地址和郵寄地址 都關聯同一個地址表
    billing_address_id = Column(Integer, ForeignKey("address.id"))
    shipping_address_id = Column(Integer, ForeignKey("address.id"))

    billing_address = relationship("Address", foreign_keys=[billing_address_id])
    shipping_address = relationship("Address", foreign_keys=[shipping_address_id])

class Address(Base):
    __tablename__ = ‘address‘
    id = Column(Integer, primary_key=True)
    street = Column(String(64))
    city = Column(String(64))
    state = Column(String(64))
    def __repr__(self):
        return self.street

engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘)
Base.metadata.create_all(engine)  # 創建表結構
MariaDB [test]> show create table customer;
| customer | CREATE TABLE `customer` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL,
  `billing_address_id` int(11) DEFAULT NULL,
  `shipping_address_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `billing_address_id` (`billing_address_id`),
  KEY `shipping_address_id` (`shipping_address_id`),
  CONSTRAINT `customer_ibfk_1` FOREIGN KEY (`billing_address_id`) REFERENCES `address` (`id`),
  CONSTRAINT `customer_ibfk_2` FOREIGN KEY (`shipping_address_id`) REFERENCES `address` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

MariaDB [test]> desc customer;
+---------------------+-------------+------+-----+---------+----------------+
| Field               | Type        | Null | Key | Default | Extra          |
+---------------------+-------------+------+-----+---------+----------------+
| id                  | int(11)     | NO   | PRI | NULL    | auto_increment |
| name                | varchar(64) | YES  |     | NULL    |                |
| billing_address_id  | int(11)     | YES  | MUL | NULL    |                |
| shipping_address_id | int(11)     | YES  | MUL | NULL    |                |
+---------------------+-------------+------+-----+---------+----------------+

正常寫的時候,表結構單獨寫一個模塊。添加數據

import orm_many_fk
from sqlalchemy.orm import sessionmaker

Session_class = sessionmaker(bind=orm_many_fk.engine)  # 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
session = Session_class()  # 生成session實例 #cursor

addr1 = orm_many_fk.Address(street="Tiantongyuan", city="ChangPing", state="BJ")
addr2 = orm_many_fk.Address(street="Wudaokou", city="Haidian", state="BJ")
addr3 = orm_many_fk.Address(street="Yanjiao", city="LangFang", state="HB")

session.add_all([addr1,addr2,addr3])
c1 =  orm_many_fk.Customer(name="Fgf", billing_address= addr1,shipping_address=addr2)
c2 =  orm_many_fk.Customer(name="Jack", billing_address= addr3,shipping_address=addr3)

session.add_all([c1,c2])
session.commit()

obj = session.query(orm_many_fk.Customer).filter(orm_many_fk.Customer.name=="Fgf").first()
print(obj.name,obj.billing_address,obj.shipping_address)  # 查詢

出處:http://blog.csdn.net/fgf00/article/details/52949973

7.3 多對多關系

現在來設計一個能描述“圖書”與“作者”的關系的表結構,需求是

  1. 一本書可以有好幾個作者一起出版
  2. 一個作者可以寫好幾本書
    此時你會發現,用之前學的外鍵好像沒辦法實現上面的需求了
    那怎麽辦呢? 此時,我們可以再搞出一張中間表,就可以了
    這樣就相當於通過book_m2m_author表完成了book表和author表之前的多對多關聯
    雙向一對多,就是多對多。
    用orm如何表示呢?
# 創建表結構
from sqlalchemy import Table, Column, Integer,String,DATE, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
# 第三張表 自己創建。不需要手動管理,orm自動維護
book_m2m_author = Table(‘book_m2m_author‘, Base.metadata,
                        Column(‘book_id‘,Integer,ForeignKey(‘books.id‘)),
                        Column(‘author_id‘,Integer,ForeignKey(‘authors.id‘)),
                        )
class Book(Base):
    __tablename__ = ‘books‘
    id = Column(Integer,primary_key=True)
    name = Column(String(64))
    pub_date = Column(DATE)
    # book表不知道第三張表,所以關聯一下第三張表
    authors = relationship(‘Author‘,secondary=book_m2m_author,backref=‘books‘)
    def __repr__(self):
        return self.name

class Author(Base):
    __tablename__ = ‘authors‘
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    def __repr__(self):
        return self.name

engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding=‘utf-8‘)
Base.metadata.create_all(engine)  # 創建表結構
MariaDB [test]> desc book_m2m_author;
+-----------+---------+------+-----+---------+-------+
| Field     | Type    | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| book_id   | int(11) | YES  | MUL | NULL    |       |
| author_id | int(11) | YES  | MUL | NULL    |       |
+-----------+---------+------+-----+---------+-------+
# 做了復合鍵了
# 添加數據
import orm_m2m
from sqlalchemy.orm import sessionmaker

Session_class = sessionmaker(bind=orm_m2m.engine)  # 創建與數據庫的會話session class ,註意,這裏返回給session的是個class,不是實例
session = Session_class()  # 生成session實例 #cursor
# 創建書
b1 = orm_m2m.Book(name="learn python with Alex",pub_date="2014-05-2")
b2= orm_m2m.Book(name="learn Zhangbility with Alex",pub_date="2015-05-2")
b3 = orm_m2m.Book(name="Learn hook up girls with Alex",pub_date="2016-05-2")
# 創建作者
a1 = orm_m2m.Author(name="Alex")
a2 = orm_m2m.Author(name="Jack")
a3 = orm_m2m.Author(name="Rain")
# 關聯關系
b1.authors = [a1,a3]
b3.authors = [a1,a2,a3]

session.add_all([b1,b2,b3,a1,a2,a3])
session.commit()
# 重要是查詢
author_obj = session.query(orm_m2m.Author).filter(orm_m2m.Author.name=="alex").first()
print(author_obj.books[0:])
book_obj = session.query(orm_m2m.Book).filter(orm_m2m.Book.id==2).first()
print(book_obj.authors)

多對多刪除
刪除數據時不用管boo_m2m_authors , sqlalchemy會自動幫你把對應的數據刪除

通過書刪除作者

author_obj =s.query(Author).filter_by(name="Jack").first()
book_obj = s.query(Book).filter_by(name="跟Alex學把妹").first()
book_obj.authors.remove(author_obj) #從一本書裏刪除一個作者
s.commit()

直接刪除作者 

刪除作者時,會把這個作者跟所有書的關聯關系數據也自動刪除

author_obj =s.query(Author).filter_by(name="Alex").first()
# print(author_obj.name , author_obj.books)
s.delete(author_obj)
s.commit()

8、中文問題

先查看數據庫的字符集

MariaDB [test]> show create database test;
+----------+-----------------------------------------------------------------+
| Database | Create Database                                                 |
+----------+-----------------------------------------------------------------+
| test     | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 */ |
+----------+-----------------------------------------------------------------+

修改數據庫的字符集(如果修改字符集,添加仍不顯示中文,可能就需要創建是指定嘗試一下了。)

MariaDB [test]> alter database test character set utf8;

創建數據庫指定數據庫的字符集

MariaDB [test]> create database mydb character set utf8;

sqlalchemy 連接指定

engine = create_engine("mysql+pymysql://root:123456@localhost/test?charset=utf8",)

python(十二)下:ORM框架SQLAlchemy使用學習