对Flask感兴趣的,能够看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002前端
要在 python
中链接数据库,则要从 sqlalchemy
中导入 create_engine
,而且要配置好数据库的信息,以下代码所示:python
# 导入模块 from sqlalchemy import create_engine # 配置数据库 DIALECT = 'mysql' DRIVER = 'mysqldb' # python2 写 mysqldb;python3 写 pymysl USERNAME = 'root' PASSWORD = 'root' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'db_demo1' # DB_URL 的格式是:dialect+driver://username:password@host:port/database # 因此要将配置变量组合成固定格式 DB_URL = "mysql+mysqldb://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,db=DATABASE) # 建立数据库引擎 engine = create_engine(DB_URL) # 判断是否链接成功 conn = engine.connect()
注意,以上方法未涉及 Flask
的内容,包括 17. Flask 下使用 SQLalchemy
节以前的内容,只是在纯 python
代码中经过 sqlalchemy
进行数据库的操做。mysql
ORM``(Object Relationship Mapping)
:对象关系映射。实际上就是模型与数据库表的映射。sql
drop database db_name; 删除数据库 create database db_name charset utf8; 建立数据库 show tables; 查询数据库中的全部 table drop table person; 删除名称为 person 的 table desc person; 查看 person 的具体属性
要在 Flask 中使用 sqlalchemy 链接数据库,应该先导入 create_engine
:数据库
from sqlalchemy import create_engine
再作好链接数据库的相关配置:flask
HOSTNAME = '127.0.0.1' PORT = '3306' DATABASE = 'mydb01' USERNAME = 'root' PASSWORD = 'root' # dialect+driver://username:password@host:port/database DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)
链接数据库安全
engine = create_engine(DB_URL)
建立 ORM 模型服务器
要建立 ORM
模型,这个模型必须继承自 sqlalchemy
给咱们定义好的基类。这个基类是经过 sqlalchemy.ext.declarative
下的一个函数(declarative_base
)来初始化的,因此咱们要先导入它:session
from sqlalchemy.ext.declarative import declarative_base
导入完成后,还须要使用它进行基类的初始化,即便用它建立一个基类:app
Base = declarative_base(engine) # 指定数据库引擎建立一个基类,并赋给 Base 变量
其中,基类和类与对象之间的关系是:基类建立类 -> 类实例化出对象。建立这个基类,是由于这个基类已经帮咱们封装好了映射到数据库中的一些方法,自定义的模型若继承自这个基类,会更方便咱们经过 python
去操做数据库。
完成以上步骤后,就能够经过建立出来的基类再建立一个类,而这个类就是 ORM
中的模型,以下:
class Person(Base): # 必须继承自基类 __tablename__ = 'person' # 使用 __tablename__ 来指定映射到数据库中的表名
定义模型属性,即定义数据库表的字段
上一步的代码,只是建立了一个能够映射到数据库中的一个表,可是该表并无任何字段,咱们须要完善其中的属性。而这些属性在数据库中是一个个的数据类型(如 int
| char
| varchar
等),这些数据类型也在 sqlalchemy
中定义好了,咱们能够直接用,但要先导入:
from sqlalchemy import Column,Integer,String class Person(Base): __tablename__ = 'person' id = Column(Integer,primary_key=True,autoincrement=True) # 一个 Column 能够定义表中的一个列,能够在括号内指定数据类型(Integer),主键,自增加等数据库属性 name = Column(String(10)) age = Column(Integer)
将建立好的模型映射到数据库中
Base.metadata.create_all()
须要注意的是:一旦使用 Base.metadata.create_all()
将模型映射到数据库中,以后若要改变表中的字段(添加字段或删除字段)再从新映射,那么是不会生效的。
去数据库中验证是否成功
show tables;
完整代码以下:
from sqlalchemy import create_engine,Column,Integer,String from sqlalchemy.ext.declarative import declarative_base HOSTNAME = '127.0.0.1' PORT = '3306' DATABASE = 'mydb01' USERNAME = 'root' PASSWORD = 'root' # dialect+driver://username:password@host:port/database DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE) engine = create_engine(DB_URL) Base = declarative_base(engine) # 1. 建立一个 ORM 模型,这个模型必须继承自 sqlalchemy 给咱们定义好的基类 class Person(Base): __tablename__ = 'person' # 2. 在这个 ORM 模型中建立一些属性,对应于表中的一些字段,而这些属性必须是 sqlalchemy 给咱们定义好的数据类型 id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(10)) age = Column(Integer) # 3. 将建立好的 ORM 模型映射到数据库中 Base.metadata.create_all()
魔术方法
在定义一个类的时候,能够定义一个 __str__()
方法,这个方法的做用是:当这个类被 print
方法调用时,那么 print
会首先调用这个类里面的 __str__()
方法。而在 __str__()
方法中,通常都是 return
一个字符串。例如,对刚刚的类定义的 __str__()
方法:
class Person(Base): __tablename__ = 'person' id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(10)) age = Column(Integer) def __str__(self): return 'Person(name:%s,age:%s)' % (self.name,self.age)
全部的数据库 ORM
操做都必须经过一个叫作 session
的会话对象来实现,那么这个对象的建立是经过如下代码来实现的:
from sqlalchemy.orm import sessionmaker engine = create_engine(DB_URL) Session = sessionmaker(engine) session = Session()
其中:sessionmaker
和 declarative_base
的原理相似,也是一个方法。后者接收一个 engine
建立一个基类,再建立一个模型;前者也要接收一个 engine
,从而对 engine
中的数据进行操做(增删改查等)。
后两行代码能够简化写成:session = sessionmaker(engine)()
。
建立对象,并使用 session
对象添加且提交:
p = Person(name='myyd',age=18) # 建立对象(对于自增加的属性,主键不用写) session.add(p) # 将对象添加到会话对象中 session.commit() # 使用 commit 将会话中的对象提交到数据库中
若是要添加多条数据,则必须以列表的形式传给 session
对象的 add_all()
方法:
p1 = Person(name='MYYD',age=19) p2 = Person(name='Myyd',age=20) session.add_all([p1,p2]) # 注意是 add_all session.commit()
能够查询所某个数据库中某个表的全部数据,也可使用条件查询该表中的符合条件的数据。
查找表中的全部数据:
person_all = session.query(Person).all() for person in person_all: print(person)
查找表中符合条件的全部数据(方法一):
person_all = session.query(Person).filter_by(age=18).all() for person in person_all: print(person)
查找表中符合条件的全部数据(方法二):
person_all = session.query(Person).filter(Person.age==18).all() for person in person_all: print(person)
查找表中符合条件的全部数据(区别):
区别在于,filter()
要指定类名,而且判断时要使用双等于号 ==
,要相对麻烦一点。可是这两种方法,在大项目中是会同时用到的,因此两个都要学会!
使用 get()
方法根据主键查找数据:
get()
方法会根据表中的主键进行查找数据,如有则返回数据,若无则返回 None。
person1 = session.query(Person).get(1) person2 = session.query(Person).get(100) print(person1,person2) # 会返回 get(1) 的数据,get(100) 的数据是 None
使用 first()
方法获取表中的第一条数据:
person3 = session.query(Person).first() print(person3)
要修改表中的数据,思路很简单:先经过查询,将指定数据选出来并赋予一个变量;再修改该变量的属性;最后用 session.commit()
提交便可。以下所示:
person = session.query(Person).first() person.name = 'mayiyoudu' session.add(person) session.commit()
和修改相似,先查询找到指定的数据,再经过 session
进行删除。以下所示:
person = session.query(Person).first() session.delete(person) session.commit()
Integer
),有微整型,整型和长整型Float
,Double
),Float
是 32
位;Double
是 64
位Boolean
),在数据库中能够用微整型(0
和1
)来实现DECIMAL
),用来处理精度丢失的问题,至关于将输入的浮点数当成文本处理Enum
),只能输入预先指定的内容Date
),只能存储年月日,传入datetime.date()
DateTime
),能够存储年月日时分秒,传入datetime.datetime()
Time
),只能存储时分秒,传入datetime.time()
String
),至关于数据库中的varchar类型,便可变长字符类型Text
),至关于数据库中的text类型,最多只能存储6W多个字LONGTEXT
),若是文字较多,可用LONGTEXT类型,只MySQL支持,要从另外的包中导入下面咱们分别来介绍各个数据类型的特性:
整型、浮点型、文本类型、字符类型比较经常使用,咱们放到一个例子来说。以下代码所示:
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=True) content = Column(Text,nullable=True) price = Column(Float,nullable=True) article = Articles(title='MYYD',content='HelloWorld',price=12.34563)
上述代码表示:
- id 字段为整型,其值必须为一个整数;
- title 字段为字符类型,对应数据库中的 varchar 类型,是一个可变长度的字符,括号中的数字为该字段所能接受的最大字母数;
- content 字段为文本类型,可接受最大字符长度为6W多字;
- price 字段为浮点类型,对于 Float 来讲只能表示4位小数,对于 Double 来讲能够接受8位小数,若是数字太大,可使用定点类型。
定点类型
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) price = Column(DECIMAL(10,6)) article = Articles(price=2345.67891)
其中,DECIMAL(10,6)
表明一共只能表示10个数字,分别是:整数最多只能有4位,小数最多只能表示6位。若是小数位数多了则四舍五入表示6位;若是整数位数多了,则直接报错。即小数位数能够大于6而整数位数不能够大于4。
日期时间类型
from datetime import date,datetime,time class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) create_date = Column(Date) create_datetime = Column(DateTime) create_time = Column(Time) article = Articles(create_date=date(2011,11,11),create_datetime=datetime(2011,11,11,11,11,11),create_time=time(11,11,11))
要使用日期和时间,须要另外从 datetime
模块中导入 date
,datetime
,time
;与模型字段中的三个数据类型 Date
,Datetiem
,Time
一一对应。
枚举类型(方法一)
class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) Language = Column(Enum('Python','Java','PHP','C++')) article = Articles(Language='python')
其中,在建立实例的时候,Language
字段必需要从枚举指定的 Python
Java
PHP
C++
中选择,不然报错。
枚举类型(方法二)
定义枚举类型还有另一种更好的方法,就是借助 python3
中自带的 enum
模块进行定义。要注意四个地方:
- 导入
enum
模块- 建立所需的
enum
类并继承自enum.Enum
- 建立模型而且使用枚举数据类型时,从自定义的
enum
类中引用- 根据模型建立实例时也能够从自定义的
enum
类中取
以下代码所示:
import enum class MyEnum(enum.Enum): Python = 'Python' Java = 'Java' PHP = 'PHP' C = 'C++' class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) Language = Column(Enum(MyEnum)) article = Articles(Language=MyEnum.Python)
长文本类型
长文本类型只有 MySQL
数据库才支持,因此若是想使用长文本类型,则须要从 sqlalchemy.dialects.mysql
中导入:
from sqlalchemy.dialects.mysql import LONGTEXT class Articles(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) content = Column(LONGTEXT) article = Articles(content='over 60000 words')
primary_key
:设置是否为主键autoincrement
:设置是否为自增加default
:设置默认值,即当实例化的时候没有指定该属性的值时,该属性的值。能够在 create_time
属性中使用。nullable
:设置该属性的值是否能够为空,若是没有给该属性设置该参数,则默认为 True
,即默承认空。但主键默认为 False
。unique
:设置是否惟一,好比手机号码、邮箱等属性,都是惟一的,即要指定 unique
为 True
。不设置时,默认是 False
。onupdate
:若设置了该属性,则当其余属性有改动的时候,该属性也会更改。最典型的应用是:
update_time
用来设置文章的更新时间,当文章的标题或者内容被更新时,update_time
也会随之被更新,以下代码所示:
class Aricles(Base) __tablename__ = 'articles' id = Column(Integer,primary_key=True) title = Column(String(50),nullable=False) content = Column(Text,nullable=Flase) update_time=Column(DateTime,onupdate=datetime.now,default=datetime.now) article = Articles(datetime.now())
当对象 article
被建立后,在某一时刻其 title
或者 content
属性被修改,那么其 update_time
属性因为被指定了 onupdate=datetime.now
参数,也会随之更改。
onupdate
的值,而是使用 default
的值。name
:用来指定某个模型中的属性映射到数据库后,该属性对应字段的名称。也就是说,你在定义模型的时候,有一个 title
属性,可是你想让该属性映射到数据库中的时候变成其余名字的字段,就可使用 name 参数来实现。如:
class Aricles(Base) id = Column(Integer,primary_key=True) __tablename__ = 'articles' title = Column(String(50),nullable=False,name='My_title') # 若是把 name 参数放到该属性第一个位置,则不须要 name 关键字,以下便可: title = Column('My_title',String(50),nullable=False) # 可是不能够把该参数放到第一个位置的同时还指定参数名,由于关键字参数必需要放在未知参数以后!(name='myyd'这种叫关键字参数;int(12)这种叫未知参数)。 title = Column(name='My_title',String(50),nullable=False) # 这样是不行的
准备工做:
在查询以前,咱们须要在建立好的模型中定义 __repr__()
函数,这个函数的做用是:当用 print
输出由这个类组成的列表时,会按照这个函数定义的格式输出。
要注意与 __str__()
函数的区别,__str__()
函数是只有在 print
输出去这个类的具体实例的时候才会被调用。
两个函数的定义实现以下:
def __str__(self): return 'id=%s;title=%s;price=%s' % (self.id,self.title,self.price) def __repr__(self): return 'id=%s;title=%s;price=%s' % (self.id, self.title, self.price)
能够用 query
获取 ORM
模型中实例的全部属性:
result = session.query(Articles).all() for article in result: print(article)
能够用 query
获取 ORM
模型中实例的指定属性:
result = session.query(Articles.title,Articles.price).all() for article in result: print(article)
能够用 query
内置的一些聚合函数来实现对查找到的数据作进一步的操做,在使用这些聚合函数以前要先导入这些函数所在的类(func
)。
from sqlalchemy import func
这些聚合函数是:
func.count
:统计行的数量
session.query(func.count(Articles.id)).first() # 注意要指定表的字段,即模型的属性 # 输出该表中的数据条目数量,例如:有六条数据就输出(6,)
func.avg
:求平均值
求平均值也是相似的用法:
result = session.query(func.avg(Articles.price)).first() print(result) # (78.28571428571429,)
func.max
:求最大值
result = session.query(func.max(Articles.price)).first() print(result) # (97,)
func.min
:求最小值
result = session.query(func.min(Articles.price)).first() print(result) # (51,)
func.sum
:求和
result = session.query(func.sum(Articles.price)).first() print(result) # (548.0,)
实际上,func
对象中并无定义任何函数,由于它底层的实现是把传入的函数翻译成 sql
语句后再进行操做。因此只要是 mysql
中有的聚合函数,均可以使用 func.
来调用。
equal
article = session.query(Articles).filter(Articles.id==1).all() print(article)
not equal
articles = session.query(Articles).filter(Articles.id!=1).all() print(articles)
like & ilike
(不区分大小写)
articles = session.query(Articles).filter(Articles.title.like('MYY%')).all() # 其中,% 在 sql 语句中是通配符 print(articles)
in
这里要注意,使用的 in 的时候要传入一个列表,最终查找的结果是:既在数据库中又是列表中指定的数据被找到。
articles = session.query(Articles).filter(Articles.title.in_(['MYYD','title1','title2'])).all() # 为何是 in_ ?由于 python 中有关键字 in,为了区分因此不能用 in,而 _in 表明了类中的私有方法,但这里很明显应该是公有方法,因此定义为 in_ print(articles)
not in
即相对于上例而言,取反的结果。
方法一:波浪号
articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])).all() # 注意这里要加一个波浪号 print(articles)
方法二:notin_()
articles = session.query(Articles).filter(Articles.title.notin_(['MYYD','title1','title2'])).all() # 注意这里是 notin_() print(articles)
is null
和 is not null
用来根据某个字段是否为空来查找数据,以下:
articles = session.query(Articles).filter(Articles.content==None).all() print(articles) # 查找 Articles.content 字段为空的数据
is not null
的示例以下:
articles = session.query(Articles).filter(Articles.content!=None).all() print(articles) # 查找 Articles.content 字段为空的数据
and
用来查找更精细的范围,如 content='abc'
而且 title='MYYD'
的数据,以下:
articles = session.query(Articles).filter(Articles.content=='abc',Articles.title=='MYYD').all() print(articles)
会查找到 title 为 MYYD 而且 content 为 abc 的数据条目。
or
只要知足指定条件之一便可。以下:
from sqlalchemy import or_ articles = session.query(Articles).filter(or_(Article.title=='MYYD',Articles.content=='MYYD')).all() print(articles)
用得比较多的状况是,搜索一个关键字,这个关键字可能出如今标题中,也可能出如今内容中。这里要注意的是:要从 sqlalchemy 中导入 or_ 这个方法。
实际上,and 也有这种方法,以下:
from sqlalchemy impor and_ articles = session.query(Aritlces).filter(and_(Articles.title=='MYYD',Articles.content=='abc')).all() pint(articles)
小知识,若是想要获取翻译成的 sql
语句,能够在查询的时候不加 .all()
或者 .first()
,以下:
articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])) print(articles) 输出以下: SELECT articles.id AS articles_id, articles.title AS articles_title, articles.price AS articles_price FROM articles WHERE articles.title NOT IN (%(title_1)s, %(title_2)s, %(title_3)s, %(title_4)s)
外键可使表与表之间的关系更加紧密,Sqlalchemy
也支持对外键的操做。而 Sqlalchemy
操做外键是经过 ForeignKey
来实现的。最多见的例子是:有用户和文章这两张表,每张表都有本身的属性,可是文章是经过用户来发表的,因此这两张表中必然存在某种联系;使用外键就能够将两张表联系起来。
那么怎么使用 sqlalchemy
建立两张具备约束关系的表呢?以下所示:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意这个地方要使用数据库表名,而不是建立模型时的类名
使外键有四个能够约束的功能,在使用外键链接时用 ondelete
关键字指定。指定方法是:
user_id = Column(Integer,ForeignKey('users.id',ondelete='RESTRICT')) user_id = Column(Integer,ForeignKey('users.id',ondelete='NO ACTION')) user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE')) user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
四个约束功能介绍以下:
RESTRICT
:父表数据被删除时会因为子表数据还在引用而阻止删除
这个约束是默认就会存在的,不用特地指定。
NO ACTION
:与 MySQL
中的 RESTRICT
相同
既然相同,那么也不用特地指定。
CASCADE
:级联删除,即当父表中的某个条目被删除了,子表中关联了该条目的数据也会被删除
user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
SET NULL
:父表中的某个条目被删除,子表中关联了该条目的数据不会被删除,可是外键字段会被置为 NULL
user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
即原来是:
mysql> select * from articles; +----+-------+---------+---------+ | id | title | content | user_id | +----+-------+---------+---------+ | 1 | NB | abc | 1 | +----+-------+---------+---------+
当 users
表中 id=1
的数据被删除后,articles
表中关于该字段的值就会被置为 NULL
:
mysql> select * from articles; +----+-------+---------+---------+ | id | title | content | user_id | +----+-------+---------+---------+ | 1 | NB | abc | NULL | +----+-------+---------+---------+
要注意的是,在使用外键时,是不能够为其设置 SET NULL
字段的同时还设置 nullable=False
的。
如今有一个需求,要查找某一篇文章标题为 NB
的做者的信息,怎么才能实现这一需求?
NB
的文章users
的外键字段的值users
表中查找到该做者代码实现以下:
article_NB = session.query(Article).filter_by(title='NB').first() print(article_NB) uid = article_NB.user_id author = session.query(User).get(uid) print(author)
要使用 ORM
提供的这个方法,就必须从 sqlalchemy.orm
模块中导入:
from sqlalchemy.orm import sessionmaker,relationship
而后在定义外键的时候,同时定义外键所连接到的表的字段,并指定 relationship
字段:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意这个地方要使用数据库表名,而不是建立模型时的类名 author = relationship('User') # 注意这个地方要使用建立模型时的类名,而不是数据库表名
作好上述定之后,就能够直接经过 relationships
提供的方法,来获取文章的做者了,以下所示:
article = session.query(Article).filter(Article.title=='NB').first() print(article.author) # article.author 便是所要找的做者 print(article.author.username) # 打印做者的用户名
上面的例子是:查找一篇文章对应的做者,那么文章和做者之间的关系是多对一的,即一个做者可能发表多篇文章,而一篇文章只能有一个做者。
在 8.2
中,已经使用 relationship
引用了文章的做者,那么可否使用 relationship
引用做者的文章呢?答案是确定的,以下所示:
from sqlalchemy.orm import sessionmaker,relationship class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) articles = relationship('Article')
经过做者找文章的方法和经过文章找做者的方法相似,以下:
user = session.query(User).filter_by(username='MYYD').filter() print(user.articles) # 会打印出该 user 的全部 article
在以前的例子中,咱们在 User
和 Article
中都分别使用了 relationship
来互相指定对方和本身的关系。实际上,不用这么麻烦,咱们只须要在一个模型中指定其和另外一个模型的关系便可,这时须要借助另外一个参数:bakeref
。
因此咱们只须要在 Article
模型中指定便可:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) author = relationship('User',backref='articles')
这样,一样可使得 user.articles
可以正常使用。而不用再在 User
模型中使用 relationship
再指定一次关系了。
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(20),nullable=False) def __repr__(self): return 'username:%s'% self.username class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL')) author = relationship('User',backref='articles') def __repr__(self): return 'title:%s\tcontent:%s'%(self.title,self.content)
在指定了一对多的关系后,如 users
和 articles
这二者之间的关系。实际上,在建立 articles
的时候能够不用指定其 user_id
字段的值,当一篇文章被建立完成后,再指定做者也是被容许的。由于在一对多的关系中,users
的 articles
属性是一个 LIST
,可使用 append
方法为其添加值。以下代码所示:
article1 = Article(title='abc',content='abc') article2 = Article(title='MYYD',content='myyd') session.add_all([article1,article2]) user1 = User(username='MYYD') session.add(user1) user1.articles.append(article1) # 使用 append() 方法 user1.articles.append(article2) session.commit() user = session.query(User).filter_by(username='MYYD').first() print(user.articles) # 输出 [title:abc content:abc, title:MYYD content:myyd]
以上,是对 ORM
中一对多关系的一点补充。借此,咱们能够引出 ORM
中一对一的关系:
有一个需求:为用户建立一个 users
表,表中定义了一些属性,这些属性有:姓名,学校等。其中学校这个属性不是经常使用属性,咱们把它放到另一张 extends
表中。这样一来,两张表就是一对一的关系了。那么,怎么用 ORM
来实现这一需求呢?能够在建立 extends
表的时候在使用 relationship
参数的字段中,传入 uselist=False
参数,即不能够 uselist
,以下代码所示:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) def __repr__(self): return 'id:%s\tusername:%s'%(self.id,self.username) class UserExtend(Base): __tablename__ = 'extends' id = Column(Integer,primary_key=True,autoincrement=True) school = Column(String(20)) user_id = Column(Integer,ForeignKey('users.id')) my_user = relationship('User',backref=backref('extends',uselist=False)) # 注意这里使用 relationship 参数的方法,和传入 backref 与以前的区别 def __repr__(self): return 'school:%s'%self.school user1 = User(username='MYYD') session.add(user1) extend = UserExtend(school='JMU') session.add(extend) user1.extends.append(extend) # 此时再使用 append 方法时就会报错:AttributeError: 'NoneType' object has no attribute 'append' session.commit()
这就是 ORM
中一对一关系的使用。
多对多关系的需求经常使用在文章与标签上,譬如能够按照内容划分文章的标签为:音乐、体育、娱乐、搞笑等;而一篇文章可能涉及多个方面的内容,这时候文章和标签就是多对多的关系。可是咱们使用 ORM
定义两张表的多对多关系时,须要借助一个中间表来实现。
为两张表建立多对多关系的步骤以下:
导入定义中间表所需的 Table
类:
from sqlalchemy import Table
建立两张表(artciels
和tags
):
按照常规的方法建立两张表便可:
class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) content = Column(Text) def __repr__(self): return 'title:%s\tcontent%s'%(self.title,self.content) class Tag(Base): __tablename__ = 'tags' id = Column(Integer,primary_key=Text,autoincrement=True) name = Column(String(50),nullable=False) def __repr__(self): return 'TagName:%s'%self.name
重点在这里!建立中间表:
这个中间表用来链接刚刚定义的两张表,中间表必须继承自这个 Table
类。而且还要使用 metadate
进行初始化,同时要设置聚合主键,以下所示:
article_tag = Table( 'article_tag', Base.metadata, Column('articles_id',Integer,ForeignKey('articles.id'),primary_key=True), Column('tags_id',Integer,ForeignKey('tags.id'),primary_key=True) # tag_id 是字段名称,Integer 的类型要和本表外键引用的字段类型相同 )
其中:
Base.metadata
用来对这个中间表进行初始化;- 而后用
Column
对其进行相关字段的建立。- 为两个字段同时设置主键,这样就能两张表中的惟一一篇文章。
使用 relationship
关联两张表:
最后,还要在两张表的其中一张表中使用 relationship
参数来互相关联一下,同时指定 secondary
参数来指定经过实现多对多关系的中间表,以下:
在 articles
表中使用:
tags = relationship('Tag',backref='articles',secondary=article_tag)
在 tags
表中使用:
articles = relationship('Article',backref='tags',secondary=article_tag)
须要注意的是:若是使用中间表来实现多对多的映射关系后,就没必要在两张被映射的表中指定外键关系了。由于已经经过 secondary
来指定中间表格了,而中间表格会实现外键约束。
咱们先用定义的两张表建立数据并关联一下:
为每张表建立两个数据
article1 = Article(title='ABC',content='ABC') article2 = Article(title='abc',content='abc') tag1 = Tag(name='tag1') tag2 = Tag(name='tag2')
为文章添加标签
article1.tags.append(tag1) article1.tags.append(tag2)
添加并提交 session
session.add_all([article1,article2]) session.commit()
到数据库中查找
# articles 表 mysql> select * from articles; +----+-------+---------+ | id | title | content | +----+-------+---------+ | 1 | ABC | ABC | | 2 | abc | abc | +----+-------+---------+ 2 rows in set (0.00 sec) # tags 表 mysql> select * from tags; +----+------+ | id | name | +----+------+ | 1 | tag1 | | 2 | tag2 | +----+------+ 2 rows in set (0.00 sec) # article_tag 表 mysql> select * from article_tag; +-------------+---------+ | articles_id | tags_id | +-------------+---------+ | 1 | 1 | | 2 | 1 | | 1 | 2 | | 2 | 2 | +-------------+---------+ 4 rows in set (0.00 sec)
在用咱们的 query
去查询:
article = session.query(Article).first() print(article.tags) # [TagName:tag1, TagName:tag2] tag = session.query(Tag).first() print(tag.articles) # [title:ABC contentABC, title:abc contentabc]
在 ORM
层面对表中的数据进行删除,好比 articles
表中的 uid
字段经过外键引用到 users
表种的 id
字段。在使用 sql
语句对 users
表中的某个数据进行删除的时候,会因为 articles
表中的外键引用,而拒绝删除。
可是在 ORM
层面使用 python
代码对表中的数据进行删除,那么其实是能够删除成功的。删除的结果是:users
表中该条数据被删除,可是引用了该条数据的 articles
表中的数据关联的 id
字段会被置为空。
这是由于,ORM
在底层对这个操做的实现分为两步:先将 articles
表中该数据的 uid
字段置为 NULL
,再删除 users
表中的数据。不过前提是,articles
中的 uid
字段容许设置为空。
可是这个机制实际上并不怎么好,由于这样可能会致使误删除的发生。那如何避免这种状况呢?实际上也很简单:在 articles
的外键字段上,设置 nullable=False
便可。
总结:
ORM
层面对数据库删除操做,会无视 mysql
级别的外键约束,要想避免这种状况发生,须要将外键字段设置为 nullable=False
。
relationship
方法中的 cascade
参数能够在建立关系的时候,指定一些属性。cascade
参数一共有如下几个值:
save-update
(默认属性)
这是一个默认属性,即当不指定 cascade
参数的时候,默认 cascade='sace-update'
。这个属性的意思是:
若是给 cascade
指定了 save-update
属性,那么在添加一条数据的时候,会同时添加该条数据关联的其余模型中的数据。例如:
Article
模型在与 User
模型创建关系的时候,若是指定了 cascade='save-update'
属性,那么当使用 session.add()
添加 Article
实例(article
)的时候,若是该实例关联的 User
实例(user
)已经建立,则将 user
也一并添加。即不用再使用 session.add(user)
进行添加。
具体的使用方法以下:
author = relationship('User',backref='articles',cascade="save-update")
若是不想让 sqlalchemy
自动执行这个操做,那么能够将 cascade
置为空,即 cascade=""
。须要注意的是,这个属性仅对 Article
这个模型生效。也就是说,置为空后,session.add(article)
不会自动执行 session.add(user)
,可是 session.add(user)
仍是会自动执行 session.add(article)
。明白?
delete
delete
的做用就是,当删除一条数据的时候,会同时删除该条数据关联的其余模型中的数据。
其使用方法和 save-update 一致。
注意,cascade
属性能够同时指定多个值,例如同时指定 save-update
和 delete
,能够这么写:
author = relationship('User',backref='articles',cascade="save-update,delete")
若是想在关联模型中反向使用该属性,还能够将 cascade
放到 backref()
方法中:
author = relationship('User',backref=backref('articles',cascade="save-update,delete",cascade="save-update,delete")) # 若是 backref 只接收一个参数,能够写成 backref='参数',若是接收多个参数,能够调用 backref() 方法
delete-orphan
若是父表中的相关数据被删除,会使得子表中的数据的某个字段置为空。若是指定了 cascade='delete-orphan'
,那么因为父表的数据不存在,子表中的数据也会被删除。不过在指定 delete-orphan
的时候要同时指定 delete
属性。那么删除父表数据带动子表数据被删除的操做也有多是 delete
属性完成的,因此咱们能够将父表数据中关于子表数据的字段置空,这样子表的相关字段也会被置空,进而被删除。这样就能体现出 delete-orphan
的做用了。
注意这个属性只能在父表中指定,而且只能用在一对多或者多对多的关系中。
merge
(默认属性)
这个属性不只是 relationship
方法中 cascade
的属性,仍是 session
中的属性。其做用是:改变数据库中的值。例如:
users
表的 ORM
模型以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(50),nullable=False)
若表中已经存在一个用户(user1
):id=1,name='MYYD'
;若是再建立一个用户(user2
):id=1,name='myyd'
。使用 session.add(user2)
后再 commit
,会报错。由于 id
是主键,session.add()
不容许添加主键重复的数据。
那么若是使用 session.merge(user2)
,则会用 user2
的 id
去匹配 user
表中的 id
字段,并将匹配中的数据中的 name
改成 myyd
。
实际上这个属性用得比较少。由于这对于已经存在的数据,至关于修改操做,而修改操做彻底没必要要使用这种方法。那么这个方法的存在还有什么意义呢?
这个方法还真有本身存在的意义,由于这种方法能够添加数据,而修改操做却不能添加。使用 session.merge()
的时候,首先会判断表中是否已经存在这条数据,若存在则修改;若不存在则添加。
除此以外,还有一个做用:若是在父表的 cascade
中指定了这个属性,那么父表下全部关联的子表,在使用 session.merge(父表数据)
的时候,也会实现这个属性。
expunge
expunge
属性也是删除,用 session.expunge()
实现。session.expunge()
与 session.delete()
的区别是:前者仅仅是将数据从 session
中删除,并不会删除数据库中的内容。这是 expunge
在 session
中的体现。
而 expunge 在 cascade
中的体现和 merge
相似:当在父表的 cascade
中指定了这个属性,那么在使用 session.cascade(父表数据)
时,父表下全部关联的子表,也会实现这个属性。
all
若是在 cascade
中指定了 all
属性,那么就至关于包含了以上除了 delete-orphan
外的全部属性。
order_by
能够在查询的时候,使用 order_by
参数对查询到的数据进行排序。默认排序是从小到大,从前到后,若是想使用降序排序则在前面加一个 -
号。
articles = session.query(Article).order_by(-Article.create_time).all() for article in articles: print(article)
或者使用 desc 进行排序:
articles = session.query(Article).order_by(Article.create_time.desc()).all() for article in articles: print(article)
或者还能够直接传入模型字段的名称,搭配 -
号使用:
articles = session.query(Article).order_by('-create_time').all() for article in articles: print(article)
模型中的 order_by
若是对于一个常常须要排序的模型来讲,在每次查询的时候都使用 order_by
参数进行排序,会有点麻烦,因此咱们再定义模型的时候就能够定义一个 order_by
属性。以下:
先在模型中定义 __mapper_args__
属性:
__mapper_args__ = { 'order_by':create_time.desc() }
而后查询的时候就没必要指定 order_by 关键字了:
articles = session.query(Article).all() for article in articles: print(article)
输出以下:
title:zhenniubi create_time:2018-03-28 23:33:32 title:MYYD create_time:2018-03-28 23:33:07
可是若是想在某一次查询中不使用倒序排序呢?很简单,在查询的时候指定 order_by
关键字便可。
在 relationship
中使用 order_by
参数
能够在定义 relationship
关系的时候直接指定父表下子表的排序方式,以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',order_by=create_time.desc()))
当搜索到一个 user
时,获取 user
下的全部 articles
,这些 articles
的排序方式就是 order_by=create_time.desc()
指定的倒序。以下:
user = session.query(User).filter(User.id=='1').first() for article in user.articles: print(article) # 打印的内容以下: title:title2 create_time:2018-03-29 00:19:18 title:title1 create_time:2018-03-29 00:19:13
limit
这个属性能够对每次查询的结果数量进行限制,例如能够在查询时只取 10
个数据:session.query(Article).limit(10).all()
。
须要注意的是,使用以上操做进行数据的查询时,默认是从第 1
条数据进行查找的。若是咱们想要从指定位置开始查找,可使用 offset
操做:
offset
这个属性能够指定咱们开始查找的位置,例如想要获取第十一到第二十条数据,能够这么操做:session.query(Article).offset(10).limit(10).all()
。
这个 offset
和 limit
谁先谁后无所谓。
实际上,经过 articles = session.query(Article).all()
获得的数据,articles 是一个列表变量,因此咱们能够经过列表中的切片属性,来获取指定的内容。例如想要从获取到的全部数据中提取指定范围内的数据(如第3个到第7个数据),有两种方法能够实现:
slice
方法
这种方法是:article = session.query(Article).slice(2,7).all()
list
切片方法
这种方法是:articles = session.query(Article).all()[2:7]
。
以上三个操做,均可以搭配上一节中提到的排序方法灵活使用。
在现实的项目中,经常会遇到这种需求:当我点进一个用户的主页中,该用户的主页会显示当天发表的文章,而不是其全部的文章。要实现这个功能实际上很简单:先将该用户的全部文章都查找出来,而后对每一篇的建立时间进行过滤后再渲染回前端。以下代码所示:
articles = session.query(Article).all() for article in articles: if article.create_time.day == 29: print(article)
以上方法,确实简单粗暴。可是我只须要展现该用户当天的文章,实际上不必从数据库中查找出全部的文章,毕竟这么作很浪费资源。咱们可使用 sqlalchemy
给咱们提供的懒加载技术,实现从数据库中查找出来的文章就是当天发表的文章的需求。
可是咱们使用 articles = session.query(Article).all()
获取到的对象是一个 LIST,而 LIST 是没有办法进行进一步的 filter 的。可是咱们的 Query 对象是能够进行 filter 的,因此咱们能够将 articles 转换成 Query 对象,而后再对 articles 进行过滤。想要实现这个功能,那么在建立模型关系的时候必需要在 relationship 中的 backref() 方法中,添加一个 lazy=dynamic
值。以下:
class Article(Base): 略.. author = relationship('User',backref=backref('articles',lazy='dynamic'))
而后使用 articles = session.query(Article).all()
查找到的 articles
就是一个 AppenderQuery
对象,该对象除了能够实现 Query
对象的全部功能(包括filter
),还能实现 LIST 的全部功能(包括append
)。
因此这个需求咱们就能垂手可得的实现了:
user = session.query(User).filter_by(id=1).first() print(type(user.articles)) # 输出:<class 'sqlalchemy.orm.dynamic.AppenderQuery'> print(user.articles.filter(Article.create_time.day==29).all()) # 输出全部 29 号发表的文章 # 注意这里是去数据库中筛选符合条件的数据,而不是将全部的数据都取出来,这样就节省了资源的消耗
注意:这个地方 lazy='dynamic'
只能对一对多
或者 多对多
中的 多
使用,即对于上述两个模型,只能用在 User
对 Article
上,也就是说对符合条件的文章的过滤只能经过 user.articles.filter()
操做进行实现。
附:相对完整的代码以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',lazy='dynamic')) # 注意这里 user = session.query(User).first() print(user.articles.filter(Article.id>9).all())
这个是 sqlalchemy
中的默认选项,即若是不设置 lazy='xxx'
这个属性,则默认为 lazy='select
' 生效。
lazy='select'
的做用是,在没有调用 user.articles
以前,sqlalchemy
不会去获取 user.articles
这个属性下的数据;而一旦调用了 user.articles
,那么 sqlalchemy
就会自动的去获取 user.articles
这个属性下的全部数据。
这也就是为何咱们在实现上述需求的时候,使用 lazy='dynamic'
的另外一大缘由。
对于定义的 ORM 模型,和插入的数据以下:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) age = Column(Integer,nullable=False,default=0) gender = Column(Enum('male','female','secret'),default='secret') user1 = User(username='张伟',age=13,gender='male') user2 = User(username='无名',age=14,gender='secret') user3 = User(username='翠花',age=19,gender='female') user4 = User(username='狗剩',age=17,gender='male') user5 = User(username='李蛋',age=16,gender='female') session.add_all([user1,user2,user3,user4,user5]) session.commit()
group_by 会根据指定的某个字段进行分组。例如,要根据性别来分组,统计每一个性别的人数,可使用以下代码实现:
result = session.query(User.gender,func.count(User.id)).group_by(User.gender).all() # 指定查询 User.gender,而且以 User.id 为标识来统计,最后按 User.gander 进行分组 print(result) # 输出:[('male', 2), ('female', 2), ('secret', 1)]
实际上转换成的 SQL
语句以下:
SELECT users.gender AS users_gender, count(users.id) AS count_1 FROM users GROUP BY users.gender
having
是对查找结果的进一步过滤,和 sql
语句中的 where
相似。例如:查看未成年人的人数,能够先对年龄进行分组统计,再对分组进行过滤。代码以下所示:
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age<=18).all() print(result) # 输出:[(13, 1), (14, 1), (16, 1), (17, 1)]
先来段代码压压惊:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) def __repr__(self): return 'username:%s'%self.username class Article(Base): __tablename__ = 'articles' id = Column(Integer,primary_key=True,autoincrement=True) title = Column(String(50),nullable=False) create_time = Column(DateTime,nullable=False,default=datetime.now) uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles')) def __repr__(self): return 'title:%s\tcreate_time:%s'%(self.title,self.create_time) user1 = User(username='mayi') user2 = User(username='youdu') for i in range(1): article = Article(title='title %s'%i) article.author = user1 session.add(article) session.commit() for i in range(1,3): article = Article(title='title %s'%i) article.author = user2 session.add(article) session.commit() # 以上代码建立了两个模型:User 和 Article。 # 其中 Article 的 uid 属性与 User 的 id 属性创建了外键约束,而且两者相互调用的方法是 User.articles 和 Article.author。 # User 创建了两个实例 user1 和 user2 # Article 创建了三个实例 title0、title1 和 title2 # 其中,user1 关联了 titile0,user2 关联了 title1 和 title2
join
分为 left join(左外链接)
、right join(右外连接)
和内连接(等值连接)
,其中左外连接将A表连接到B表的左边,右外连接将A表连接到B表的右边;且老是以左表为主表,右表为副表。join
方法其实是两张表联合在一块儿进行查询,例如:要对全部用户按发表文章的数量进行进行由大到小的排序,可使用以下代码实现。
# 原生 SQL 语句查询以下 select users.username,count(articles.id) from users join articles on users.id=articles.uid group by users.id order by count(articles.id) desc; # sqlalchemy 方式查询以下 result = session.query(User,func.count(Article.id)).join(Article,User.id==Article.uid).group_by(User.id).order_by(func.count(Article.id).desc()).all() print(result) # 输出:[(username:youdu, 2), (username:mayi, 1)]
用 sqlalchemy
查找时候要注意几点:
query()
括号中的参数决定,而不是由 join
链接的两张表决定。join
的两张表之间有且只有一个外键创建起了关系,那么在 join
的时候就能够不写 User.id==Article.uid
。join
完成;左外链接用 outterjoin
来完成;即在一个 select
中使用一个 select
。子查询能够在一次访问数据库的时候对查询结果再进行一次查询,这样能够减小对数据库的操做。当你的网站访问量很高的时候,建议使用子查询;当你的网站访问量比较少,那么能够不考虑这个问题。
例如,有以下代码:
class User(Base): __tablename__ = 'users' id = Column(Integer,primary_key=True,autoincrement=True) username = Column(String(50),nullable=False) age = Column(Integer,nullable=False,default=0) city = Column(String(20),nullable=False) user1 = User(username='张伟',age=20,city='广州') user2 = User(username='无名',age=21,city='广州') user3 = User(username='翠花',age=22,city='厦门') user4 = User(username='狗剩',age=21,city='长沙') user5 = User(username='李蛋',age=22,city='厦门') session.add_all([user1,user2,user3,user4,user5]) session.commit() # 以上代码建立了 User 模型而且为其实例化了五个对象,添加到数据库中
有一个需求:要查找和某人(如翠花)在同一个城市且年龄同样的全部人。这个需求有两种实现:
先查找出翠花,再根据翠花的信息去查找其余同城同年龄的人。
cuihua = session.query(User).filter(User.username == '翠花').first() other = session.query(User.username,User.city,User.age).filter_by(city=cuihua.city, age=cuihua.age).all() print(other)
使用子查询
SQL 语句查询:
mysql> select users.username,users.age,users.city from users,(select * from users where users.username='李蛋') as LD where users.city=LD.city and users.age=LD.age; # 其中 () 括起来的是子 select 语句,括号外面的 select 语句是根据括号内的 select 的结果进行 select 操做的。结果以下: +----------+-----+------+ | username | age | city | +----------+-----+------+ | 翠花 | 22 | 厦门 | | 李蛋 | 22 | 厦门 | +----------+-----+------+ 2 rows in set (0.00 sec)
sqlalchemy 语句查询:
temp = session.query(User.city.label('city'),User.age.label('age')).filter(User.username=='李蛋').subquery() result = session.query(User.username,User.city,User.age).filter(User.city==temp.c.city,User.age==temp.c.age).all() print(result) # 其中,temp 就是子查询获得的变量,subquery 将该查询转换为子查询,同时使用 label 为属性指定别名; # result 是根据子查询进行查询的变量,在引用时要用 'xxx.c.xx' 的形式,打印出来的结果以下: # [('翠花', '厦门', 22), ('李蛋', '厦门', 22)]
在此以前的全部学习,都是 python
下使用 sqlalchemy
操做数据库的内容,和 Flask
没有任何关系。但实际上,Flask
也帮咱们对 sqlalchemy
作了一层封装,好比将全部以前要从 sqlalchemy
中导入的东西都集成到了一个 SQLAlchemy
类中,而链接数据库时,使用 SQLAlchemy
对 Flask
对象进行初始化便可,以下所示:
db = SQLAlchemy(app)
之后全部在以前 sqlalchemy
中导入的内容,均可以从 db
这个对象中获取。例如建立 ORM
模型的时候,就不须要以前的 declarative_base
来建立基类了,直接用 db.Model
做为基类。因此能够用以下代码实现:
class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer,primary_key=True,autoincrement=True) username = db.Column(db.String(50),nullable=False) def __repr__(self): return 'username:%s'%self.username class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(50),nullable=False) uid = db.Column(db.Integer,db.ForeignKey('users.id'),nullable=False) author = db.relationship('User',backref='articles') def __repr__(self): return 'title:%s\tauthor:%s'%self.title,self.author
在建立模型的时候,能够省略 __tablename__
的表名指定动做,但不建议,由于明言胜于暗喻。
映射到数据库中也可使用以下代码实现:
db.drop_all() db.create_all()
session
的使用方法和以前的同样。其中:
增
user = User(username='MYYD') article = Article(title='abc') user.articles.append(article) db.session.add(user) db.session.commit()
删
user = User.query.filter(User.id==2).first() db.session.delete(user) db.session.commit()
改
user.query.filter(User.id==1).first() user.username = 'MYYD' db.session.commit()
查
在查询的时候,若是是针对单张表进行查询,那么直接使用 TableName.query.xxx.x
的格式便可。并且在查询的时候也可使用以前学习的 order_by
、filter
、group_by
等方法。以下代码所示:
users = User.query.order_by(User.id.desc()).all()
Flask-script
的做用是能够经过命令行的方式去操做 Flask
。安装方式:在虚拟环境下 pip install flask-script
。
新建一个 manage.py
文件,将代码写在该文件中,而不是写在主 app
文件中。内容以下:
from flask_script import Manager # 从 flask_script 导入 Manager from flask_script_demo1 import app # 从 flask_script_demo1 导入 app manage = Manager(app) # 初始化 app @manage.command # 装饰器 def runserver(): # 执行命令的程序写在这个函数下 print('服务器跑起来了。') @manage.command # 装饰器 def stopserver(): # 执行命令的程序写在这个函数下 print('服务器关闭了。') @manager.option('-u','--username',dest='username') # 还能够在执行命令的时候向命令传递参数 @manager.option('-e','--email',dest='email') #还能够在执行命令的时候向命令传递参数 def addBackendUser(username,email): user = BackendUser(username=username,email=email) db.session.add(user) db.session.commit() if __name__ == '__main__': manage.run()
命令行调用 manage.py
文件:
在虚拟环境的命令行下,用 python manage.py command
执行 manage.py
文件下的某段程序,如:python manage.py runserver
和 python manage.py stopserver
分别会执行 manage.py
文件中的 runserver()
和 stopserver()
方法。
在定义能够传递参数的命令时要注意,使用的装饰器是 @manager.option()
,以 @manager.option('-u','--username',dest='username')
为例:-u
是在传递参数时要指定的选项缩写,选项完整的写法是 --username
,dest='username'
是指该选项后面跟的参数要传递给 addBackendUser(username,email)
方法中的 username
参数。
因此在执行能够传递参数的命令时,应该这么写(例):python manage.py -u MYYD -e 90q7w0s7x@qq.com
。须要注意的是,有几个参数就要写几个 option
装饰器。
若是有一些关于数据库的操做,咱们能够放在一个文件中执行。如 db_script.py
文件:
from flask_script import Manager # 由于本文件不是做为主 app 文件,因此不须要写 if __name__ == '__main__' # 也不须要在初始化的时候传入 app 文件 DBManage = Manager() @DBManage.command def init(): print('服务器初始化完成。') @DBManage.command def migrate(): print('数据库迁移完成。')
这时候要想用上 db_script.py
里定义的命令,须要在主 manage.py
文件中导入该文件并引用该文件的命令:
from db_scripts import DBManage # 导入 db_script.py 文件 manage.add_command('db',DBManage) # 引用该文件 # 以后要是想使用 db_script.py 中的命令,命令行中就要经过 python manage.py db init 来调用 # 其中,db 是 manage.add_command() 中引号内的值,调用子命令的方法就是这种格式
若是直接在主 manage.py
文件中写命令,且该命令不须要传递参数,调用时只须要执行 python manage.py command_name
格式的命令便可
若是直接在主 manage.py
文件中写命令,且该命令须要传递参数,调用时须要执行 python manage.py command_name -[选项] 参数
格式的命令
若是将一些命令集中在另外一个文件中,那么就须要输入一个父命令,好比 python manage.py db init
(python2.7
环境)
1. manage.py # encoding:utf-8 from flask_script import Manager from flask_script_demo1 import app from db_scripts import DBManage manage = Manager(app) @manage.command def runserver(): print u'服务器跑起来了。' @manage.command def stopserver(): print u'服务器中止了。' @manager.option('-u','--username',dest='username') @manager.option('-e','--email',dest='email') def addBackendUser(username,email): user = BackendUser(username=username,email=email) db.session.add(user) db.session.commit() manage.add_command('db',DBManage) if __name__ == '__main__': manage.run() 2. db_script.py # encoding: utf-8 from flask_script import Manager DBManage = Manager() @DBManage.command def init(): print u'服务器初始化完成。' @DBManage.command def migrate(): print u'数据库迁移完成。'
以前咱们都是将数据库的模型(类)放在主 app
文件中,可是随着项目愈来愈大,若是对于加入的新的模型,咱们还放在主 app
文件中,就会使主 app
文件愈来愈大,同时也愈来愈很差管理。因此咱们能够新建一个专门存放模型的文件。如 models.py
文件用来专门存放模型的文件。
将本来在主 app
文件中定义的模型(类)移动到 models.py
文件中,可是会提示错误,因此咱们在 models.py
文件中导入主 app
文件的 db
:from models_sep.py import db
;同时,由于咱们的主 app
文件确定会操做到 models
文件中的模型,因此要在主 app
文件中导入 models
建立的模型。两个文件的完整代码下所示:
1. # 主 app 文件 from flask import Flask from flask_sqlalchemy import SQLAlchemy from models import Article app = Flask(__name__) db = SQLAlchemy(app) db.create_all() @app.route('/') def index(): return 'index!' if __name__ == '__main__': app.run() 2. # models.py 文件 from flask_script_demo1 import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
执行以上文件,会报错。
报错提示:ImportError: cannot import name Article
,出现此类报错,先排查路径和导入的内容是否有错,若保证没错,则极可能是出现循环引用。
报错缘由:循环引用,即 models_sep.py
引用了 models.py
中的 Article
,而 models.py
又引用了 models_sep.py
中的 db
,从而形成循环引用。
解决循环引用:
解决方法:将 db
放在另外一个文件 exts.py
中,而后 models_sep.py
和 models.py
都从 exts.py
中引用 db
变量,这样就能够打破引用的循环。
三个文件的代码以下:
1. # exts.py 文件 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 2. # 主 app 文件 from flask import Flask from models import Article from exts import db import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run() 3. # models.py 文件 from exts import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincre) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
总结:
分开 models
的目的是:让代码更方便管理。
解决循环引用的方法:把 db
放在一个单独文件中如 exts.py
,让主 app
文件和 models
文件都从 exts.py
中引用。
这个时候若是咱们的模型(类)要根据需求添加一个做者字段,这时候咱们须要去修改模型 Article
,修改完成咱们须要再映射一遍。可是对于 flask-sqlalchemy
而言,当数据库中存在了某个模型(类)后,再次映射不会修改该模型的字段,即再次映射不会奏效。
在数据库中删除该模型对应的表格,再将带有新字段的模型从新进行映射。
很显然,这种方式明显很简单粗暴,很是不安全,由于在企业中一个数据库中的表格是含有大量数据的,若是删除可能会形成重大损失。因此咱们须要一个能够动态修改模型字段的方法,使用 flask-migrate
。先安装:在虚拟环境下使用命令 pip install flask-migrate
便可。
使用 flask-migrate
的最简单方法是:借助 flask-script
使用命令行来对 flask-migrate
进行操做。一共有好几个步骤,分别说明一下:
新建 manage.py
文件:
新建 manage.py
文件后:
导入相应的包并初始化 manager
:
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app)
要使用 flask_migrate
必须绑定 app
和 db
migrate = Migrate(app,db)
把 MigrateCommand
命令添加到 manager
中,实际上就是添加 migrate
子命令到 manager
中
manager.add_command('db',MigrateCommand)
manage.py
文件代码以下:
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app) # 1. 要使用 flask_migrate 必须绑定 app 和 db migrate = Migrate(app,db) # 2. 把 MigrateCommand 命令添加到 manager 中 manager.add_command('db',MigrateCommand) if __name__ == '__main__': manager.run()
在 manage.py
文件中,导入须要映射的模型(类):
由于在主 app
文件中已经再也不须要对模型进行映射,而对模型的操做是在 manage.py
文件中进行的,包括 flask-migrate
动态映射,因此要导入须要映射的模型。
from models import Article
完成以上步骤后,便可到命令行中更新数据库中的模型了:
python manage.py db init
,初始化 flask-migrate
环境,仅在第一次执行的时候使用。python manage.py db migrate
,生成迁移文件python manage.py db upgrade
,将迁移文件映射到数据库的表格中python manage.py db --help