python 之路,Day11 - sqlalchemy ORM
本節內容html
- ORM介紹
- sqlalchemy安裝
- sqlalchemy基本使用
- 多外鍵關聯
- 多對多關係
- 表結構設計做業
1. ORM介紹
orm英文全稱object relational mapping,就是對象映射關係程序,簡單來講咱們相似python這種面向對象的程序來講一切皆對象,可是咱們使用的數據庫卻都是關係型的,爲了保證一致的使用習慣,經過orm將編程語言的對象模型和數據庫的關係模型創建映射關係,這樣咱們在使用編程語言對數據庫進行操做的時候能夠直接使用編程語言的對象模型進行操做就能夠了,而不用直接使用sql語言。python
orm的優勢:mysql
- 隱藏了數據訪問細節,「封閉」的通用數據庫交互,ORM的核心。他使得咱們的通用數據庫交互變得簡單易行,而且徹底不用考慮該死的SQL語句。快速開發,由此而來。
- ORM使咱們構造固化數據結構變得簡單易行。
缺點:git
- 無可避免的,自動化意味着映射和關聯管理,代價是犧牲性能(早期,這是全部不喜歡ORM人的共同點)。如今的各類ORM框架都在嘗試使用各類方法來減輕這塊(LazyLoad,Cache),效果仍是很顯著的。
2. sqlalchemy安裝
在Python中,最有名的ORM框架是SQLAlchemy。用戶包括openstack\Dropbox等知名公司或應用,主要用戶列表http://www.sqlalchemy.org/organizations.html#openstack程序員
Dialect用於和數據API進行交流,根據配置文件的不一樣調用不一樣的數據庫API,從而實現對數據庫的操做,如:github
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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...]
更多詳見:http:
//docs
.sqlalchemy.org
/en/latest/dialects/index
.html
|
安裝sqlalchemysql
1
|
pip
install
SQLAlchemy<br><br>pip
install
pymysql
#因爲mysqldb依然不支持py3,因此這裏咱們用pymysql與sqlalchemy交互
|
3.sqlalchemy基本使用
下面就開始讓你見證orm的nb之處,盤古開天劈地以前,咱們建立一個表是這樣的數據庫
1
2
3
4
5
6
|
CREATE
TABLE
user
(
id
INTEGER
NOT
NULL
AUTO_INCREMENT,
name
VARCHAR
(32),
password
VARCHAR
(64),
PRIMARY
KEY
(id)
)
|
這只是最簡單的sql表,若是再加上外鍵關聯什麼的,通常程序員的腦容量是記不住那些sql語句的,因而有了orm,實現上面一樣的功能,代碼以下express
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import
sqlalchemy
from
sqlalchemy
import
create_engine
from
sqlalchemy.ext.declarative
import
declarative_base
from
sqlalchemy
import
Column, Integer, String
engine
=
create_engine(
"mysql+pymysql://root:alex3714@localhost/testdb"
,
encoding
=
'utf-8'
, 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)
#建立表結構
|
你說,娘那個腚的,並無感受代碼量變少啊,呵呵, 孩子莫猴急,好戲在後面編程
Lazy Connecting
The Engine, when first returned by create_engine(), has not actually tried to connect to the database yet; that happens only the first time it is asked to perform a task against the database.
除上面的建立以外,還有一種建立表的方式,雖不經常使用,但仍是看看吧
事實上,咱們用第一種方式建立的表就是基於第2種方式的再封裝。
最基本的表咱們建立好了,那咱們開始用orm建立一條數據試試
1
2
3
4
5
6
7
8
9
10
11
|
Session_class
=
sessionmaker(bind
=
engine)
#建立與數據庫的會話session class ,注意,這裏返回給session的是個class,不是實例
Session
=
Session_class()
#生成session實例
user_obj
=
User(name
=
"alex"
,password
=
"alex3714"
)
#生成你要建立的數據對象
print
(user_obj.name,user_obj.
id
)
#此時還沒建立對象呢,不信你打印一下id發現仍是None
Session.add(user_obj)
#把要建立的數據對象添加到這個session裏, 一會統一建立
print
(user_obj.name,user_obj.
id
)
#此時也依然還沒建立
Session.commit()
#現此才統一提交,建立數據
|
我擦,寫這麼多代碼才建立一條數據,你表示太tm的費勁了,正要轉身離開,我拉住你的手不放開,高潮還沒到。。
查詢
1
2
|
my_user
=
Session.query(User).filter_by(name
=
"alex"
).first()
print
(my_user)
|
此時你看到的輸出是這樣的應該
1
|
<__main__.User
object
at
0x105b4ba90
>
|
我擦,這是什麼?這就是你要的數據呀, 只不過sqlalchemy幫你把返回的數據映射成一個對象啦,這樣你調用每一個字段就能夠跟調用對象屬性同樣啦,like this..
1
2
3
4
|
print
(my_user.
id
,my_user.name,my_user.password)
輸出
1
alex alex3714
|
不過剛纔上面的顯示的內存對象對址你是沒辦法分清返回的是什麼數據的,除非打印具體字段看一下,若是想讓它變的可讀,只需在定義表的類下面加上這樣的代碼
1
2
3
|
def
__repr__(
self
):
return
"<User(name='%s', password='%s')>"
%
(
self
.name,
self
.password)
|
修改
1
2
3
4
5
|
my_user
=
Session.query(User).filter_by(name
=
"alex"
).first()
my_user.name
=
"Alex Li"
Session.commit()
|
回滾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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()
|
獲取全部數據
1
|
print
(Session.query(User.name,User.
id
).
all
() )
|
多條件查詢
1
|
objs
=
Session.query(User).
filter
(User.
id
>
0
).
filter
(User.
id
<
7
).
all
()
|
上面2個filter的關係至關於 user.id >1 AND user.id <7 的效果
統計和分組
1
|
Session.query(User).
filter
(User.name.like(
"Ra%"
)).count()
|
分組
1
2
|
from
sqlalchemy
import
func
print
(Session.query(func.count(User.name),User.name).group_by(User.name).
all
() )
|
至關於原生sql爲
輸出爲
[(1, 'Jack'), (2, 'Rain')]
外鍵關聯
咱們建立一個addresses表,跟user表關聯
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from
sqlalchemy
import
ForeignKey
from
sqlalchemy.orm
import
relationship
class
Address(Base):
__tablename__
=
'addresses'
id
=
Column(Integer, primary_key
=
True
)
email_address
=
Column(String(
32
), nullable
=
False
)
user_id
=
Column(Integer, ForeignKey(
'user.id'
))
user
=
relationship(
"User"
, backref
=
"addresses"
)
#這個nb,容許你在user表裏經過backref字段反向查出全部它在addresses表裏的關聯項
def
__repr__(
self
):
return
"<Address(email_address='%s')>"
%
self
.email_address
|
The
relationship.back_populates
parameter is a newer version of a very common SQLAlchemy feature calledrelationship.backref
. Therelationship.backref
parameter hasn’t gone anywhere and will always remain available! Therelationship.back_populates
is the same thing, except a little more verbose and easier to manipulate. For an overview of the entire topic, see the section Linking Relationships with Backref.
表建立好後,咱們能夠這樣反查試試
1
2
3
4
5
6
|
obj
=
Session.query(User).first()
for
i
in
obj.addresses:
#經過user對象反查關聯的addresses記錄
print
(i)
addr_obj
=
Session.query(Address).first()
print
(addr_obj.user.name)
#在addr_obj裏直接查關聯的user表
|
建立關聯對象
1
2
3
4
5
6
7
8
|
obj
=
Session.query(User).
filter
(User.name
=
=
'rain'
).
all
()[
0
]
print
(obj.addresses)
obj.addresses
=
[Address(email_address
=
"r1@126.com"
),
#添加關聯對象
Address(email_address
=
"r2@126.com"
)]
Session.commit()
|
經常使用查詢語法
Common Filter Operators
Here’s a rundown of some of the most common operators used in filter():
-
equals:
query.filter(User.name == 'ed')
-
not equals:
query.filter(User.name != 'ed')
-
LIKE:
query.filter(User.name.like('%ed%'))
-
IN:
-
NOT IN:
query.filter(~User.name.in_(['ed', 'wendy', 'jack'])) -
IS NULL:
-
IS NOT NULL:
-
AND:
2.1. ObjectRelationalTutorial 17
query.filter(User.name.in_(['ed', 'wendy', 'jack']))
# works with query objects too:
query.filter(User.name.in_( session.query(User.name).filter(User.name.like('%ed%'))
))
query.filter(User.name == None)
# alternatively, if pep8/linters are a concern
query.filter(User.name.is_(None))
query.filter(User.name != None)
# alternatively, if pep8/linters are a concern
query.filter(User.name.isnot(None))
SQLAlchemy Documentation, Release 1.1.0b1
# use and_()
from sqlalchemy import and_
query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones'))
# or send multiple expressions to .filter()
query.filter(User.name == 'ed', User.fullname == 'Ed Jones')
# or chain multiple filter()/filter_by() calls
query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')
Note: Makesureyouuseand_()andnotthePythonandoperator! • OR:
Note: Makesureyouuseor_()andnotthePythonoroperator! • MATCH:
query.filter(User.name.match('wendy'))
Note: match() uses a database-specific MATCH or CONTAINS f
4.多外鍵關聯
One of the most common situations to deal with is when there are more than one foreign key path between two tables.
Consider a Customer
class that contains two foreign keys to an Address
class:
下表中,Customer表有2個字段都關聯了Address表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
from
sqlalchemy
import
Integer, ForeignKey, String, Column
from
sqlalchemy.ext.declarative
import
declarative_base
from
sqlalchemy.orm
import
relationship
Base
=
declarative_base()
class
Customer(Base):
__tablename__
=
'customer'
id
=
Column(Integer, primary_key
=
True
)
name
=
Column(String)
billing_address_id
=
Column(Integer, ForeignKey(
"address.id"
))
shipping_address_id
=
Column(Integer, ForeignKey(
"address.id"
))
billing_address
=
relationship(
"Address"
)
shipping_address
=
relationship(
"Address"
)
class
Address(Base):
__tablename__
=
'address'
id
=
Column(Integer, primary_key
=
True
)
street
=
Column(String)
city
=
Column(String)
state
=
Column(String)
|
建立表結構是沒有問題的,但你Address表中插入數據時會報下面的錯
1
2
3
4
5
6
|
sqlalchemy.exc.AmbiguousForeignKeysError: Could
not
determine join
condition between parent
/
child tables on relationship
Customer.billing_address
-
there are multiple foreign key
paths linking the tables. Specify the
'foreign_keys'
argument,
providing a
list
of those columns which should be
counted as containing a foreign key reference to the parent table.
|
解決辦法以下
1
2
3
4
5
6
7
8
9
10
|
class
Customer(Base):
__tablename__
=
'customer'
id
=
Column(Integer, primary_key
=
True
)
name
=
Column(String)
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])
|
這樣sqlachemy就能分清哪一個外鍵是對應哪一個字段了
5.多對多關係
如今來設計一個能描述「圖書」與「做者」的關係的表結構,需求是
- 一本書能夠有好幾個做者一塊兒出版
- 一個做者能夠寫好幾本書
此時你會發現,用以前學的外鍵好像沒辦法實現上面的需求了,由於
固然你更不能夠像下面這樣幹,由於這樣就你就至關於有多條書的記錄了,太low b了,改書名還得都改。。。
那怎麼辦呢? 此時,咱們能夠再搞出一張中間表,就能夠了
這樣就至關於經過book_m2m_author表完成了book表和author表以前的多對多關聯
用orm如何表示呢?
接下來建立幾本書和做者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Session_class
=
sessionmaker(bind
=
engine)
#建立與數據庫的會話session class ,注意,這裏返回給session的是個class,不是實例
s
=
Session_class()
#生成session實例
b1
=
Book(name
=
"跟Alex學Python"
)
b2
=
Book(name
=
"跟Alex學把妹"
)
b3
=
Book(name
=
"跟Alex學裝逼"
)
b4
=
Book(name
=
"跟Alex學開車"
)
a1
=
Author(name
=
"Alex"
)
a2
=
Author(name
=
"Jack"
)
a3
=
Author(name
=
"Rain"
)
b1.authors
=
[a1,a2]
b2.authors
=
[a1,a2,a3]
s.add_all([b1,b2,b3,b4,a1,a2,a3])
s.commit()
|
此時,手動連上mysql,分別查看這3張表,你會發現,book_m2m_author中自動建立了多條紀錄用來鏈接book和author表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
mysql> select
*
from
books;
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
|
id
| name | pub_date |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
|
1
| 跟Alex學Python | NULL |
|
2
| 跟Alex學把妹 | NULL |
|
3
| 跟Alex學裝逼 | NULL |
|
4
| 跟Alex學開車 | NULL |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
4
rows
in
set
(
0.00
sec)
mysql> select
*
from
authors;
+
-
-
-
-
+
-
-
-
-
-
-
+
|
id
| name |
+
-
-
-
-
+
-
-
-
-
-
-
+
|
10
| Alex |
|
11
| Jack |
|
12
| Rain |
+
-
-
-
-
+
-
-
-
-
-
-
+
3
rows
in
set
(
0.00
sec)
mysql> select
*
from
book_m2m_author;
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
| book_id | author_id |
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
|
2
|
10
|
|
2
|
11
|
|
2
|
12
|
|
1
|
10
|
|
1
|
11
|
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
5
rows
in
set
(
0.00
sec)
|
此時,咱們去用orm查一下數據
1
2
3
4
5
6
7
8
9
|
print
(
'--------經過書表查關聯的做者---------'
)
book_obj
=
s.query(Book).filter_by(name
=
"跟Alex學Python"
).first()
print
(book_obj.name, book_obj.authors)
print
(
'--------經過做者表查關聯的書---------'
)
author_obj
=
s.query(Author).filter_by(name
=
"Alex"
).first()
print
(author_obj.name , author_obj.books)
s.commit()
|
輸出以下
1
2
3
4
|
-
-
-
-
-
-
-
-
經過書表查關聯的做者
-
-
-
-
-
-
-
-
-
跟Alex學Python [Alex, Jack]
-
-
-
-
-
-
-
-
經過做者表查關聯的書
-
-
-
-
-
-
-
-
-
Alex [跟Alex學把妹, 跟Alex學Python]
|
牛逼了個人哥!!完善實現多對多
多對多刪除
刪除數據時不用管boo_m2m_authors , sqlalchemy會自動幫你把對應的數據刪除
經過書刪除做者
1
2
3
4
5
6
|
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()
|
直接刪除做者
刪除做者時,會把這個做者跟全部書的關聯關係數據也自動刪除
1
2
3
4
|
author_obj
=
s.query(Author).filter_by(name
=
"Alex"
).first()
# print(author_obj.name , author_obj.books)
s.delete(author_obj)
s.commit()
|
處理中文
sqlalchemy設置編碼字符集必定要在數據庫訪問的URL上增長charset=utf8,不然數據庫的鏈接就不是utf8的編碼格式
eng = create_engine('mysql://root:root@localhost:3306/test2?charset=utf8',echo=True)
6.本節做業
主題:學員管理系統
需求:
- 用戶角色,講師\學員, 用戶登錄後根據角色不一樣,能作的事情不一樣,分別以下
- 講師視圖
- 管理班級,可建立班級,根據學員qq號把學員加入班級
- 可建立指定班級的上課紀錄,注意一節上課紀錄對應多條學員的上課紀錄, 即每節課都有整班學員上, 爲了紀錄每位學員的學習成績,需在建立每節上課紀錄是,同時 爲這個班的每位學員建立一條上課紀錄
- 爲學員批改爲績, 一條一條的手動修改爲績
- 學員視圖
- 提交做業
- 查看做業成績
- 一個學員能夠同時屬於多個班級,就像報了Linux的同時也能夠報名Python同樣, 因此提交做業時需先選擇班級,再選擇具體上課的節數
- 附加:學員能夠查看本身的班級成績排名