13、Flask实战第13天:SQLAlcemy使用MySQL数据库

安装MySQL

在MySQL官网下载win版MySQL

双击运行

后面根据提示设置密码然后启动即可,这里我设置的密码是:123456

我们可以通过Navicat客户端工具连接上MySQL

address: 127.0.0.1
port: 3306
username: root
password: 123456
连接信息

 创建一个数据库heboan,字符集设置为utf8

安装pymysql和sqlalchemy

#进入cmd窗口,然后进入虚拟环境进行安装
workon flask-env
pip install pymysql
pip install sqlalchemy

 使用SQLAlchemy连接数据库

在项目目下创建一个文件learn_sql.py,然后运行此文件

ORM介绍

 随着项目越来越大,采用原生SQL的方式在代码中会出现大量的SQL语句,那么问题就出现:

  1、SQL语句重复利用率不高,越复杂的SQL语句条件越多,代码越长;会出现很多相近的SQL语句

  2、很多SQL语句是在业务逻辑中拼出来的,如果数据需要更改,就要去修改这些逻辑,比较容易出错

  3、写SQL时容易忽略web安全问题,给未来造成隐患,比如sql注入

ORM,全称Object Relationship Mapping,中文名叫做对象关系映射,通过ORM我们可以使用类的方式去操作数据库,而不用再写原生的SQL语句。通过表映射成类,把行作实例,把字段作属性。ORM在执行对象操作的时候最终还是会把对应的操作转换为数据库原生语句。使用ORM有许多优点:

  1、易用性:使用ORM做数据库的开发可以有效的减少重复SQL语句的概率,写出来的模型也更加直观、清晰

  2、性能损耗小:ORM转换成底层数据库操作指令确实会有一些开销,但从实际情况来看,这种性能损耗不足5%

  3、设计灵活:可以轻松写出复杂的查询

  4、可以移植性:SQLAlchemy封装了底层的数据库实现,支持多个关系型数据库引擎,可以轻松的切换数据库

定义ORM模型并将其映射到数据库中(创建表)

运行之后,查看数据库,person表已经创建好了

 注意:一旦使用Base.metadata.create_all()将模型映射到数据库后,即使改变了模型的字段,也不会重新映射了

SQLAlchemy对数据库增删改查操作 

SQLAlchemy使用ORM对数据库操作需要构建一个session对象,所有操作都必须通过中国session会话对象来实现

from sqlalchemy.orm import sessionmaker
...

session = sessionmaker(engine)()

说明:

sessionmasker和上面的declarative_base一样,把engine作为参数实例化出一个对象

  Session = sessionmaker(engine)

但是这样还不能直接使用Session,我们需要调用Session,我们知道直接实例.()这样运行直接上会调用__call__()方法

  session = Session()

为了简介,我们就可以写成这样

  session = sessionmaker(engine)()

def add_data():
    p = Person(name='heboan', age=18)
    session.add(p)
    session.commit()

add_data()




#一次添加多条数据
def add_data():
    p1 = Person(name='user1', age=21)
    p2 = Person(name='user2', age=22)
    session.add_all([p1,p2])
    session.commit()

add_data()
def search_data():
    # 查找某个模型表的所有数据
    # all_person = session.query(Person).all()
    # for p in all_person:
    #     print(p)

    # 使用filter_by作条件查询
    # all_person = session.query(Person).filter_by(name='heboan').all()
    # for p in all_person:
    #     print(p)

    # 使用filter作条件查询
    # all_person = session.query(Person).filter(Person.name=='heboan').all()
    # for p in all_person:
    #     print(p)
    
    # 使用get方法查找,get方法是根据主键ID来查找的,只会返回一条数据或None
    person = session.query(Person).get(1)
    print(person)

search_data()
def update_data():
    # 先找出要改的对象
    person = session.query(Person).first()
    # 直接改属性
    person.age = 20
    session.commit()

update_data()
def delete_data():
    persons = session.query(Person).filter_by(name='heboan')
    for p in persons:
        session.delete(p)
    session.commit()

delete_data()

SQLAlchemy属性常用数据类型

default: 默认值
nullable: 是否可空
primary_key: 是否为主键
unique: 是否唯一
autoincrement: 是否自增长
name: 该属性再数据库中的字段映射
onupdate: 当数据更新时会自动使用这个属性
  比如update_time = Colum(DateTime, onupdate=datetime.now, default=datetime.now)
Colum常用属性
Integer: 整形
Float: 浮点型,后面只会保留4位小数,会有精度丢失问题,占据32位
Double: 双精度浮点类型,占据64位,也会存在精度丢失问题
DECIMAL: 定点类型,解决浮点类型精度丢失问题;如果精度要求高,比如金钱,则适合用此类型
Boolean: 传递True/False进行
enum: 枚举类型
Date: 传递datetime.date()进去
Datetime: 传递datetime.datetme()进去
Time: 传递datetime.time()进去   
String: 字符类型,使用时需要指定长度,区别于Text类型
Text: 文本类型,一般可以存储6w多个字符
LONGTEXT: 长文本类型
  from sqlalchemy.dialects.mysql import LONGTEXT
  因为LONGTEXT只在MySQL数据库中存在
常用数据类型
...
from sqlalchemy import Column, Integer, String, Enum, Date, Float, DECIMAL, Text, Boolean, DateTime
import datetime
...


class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    gender = Column(Enum('m', 'f'), default='m')
    birthday = Column(Date)
    height = Column(Float)
    wages = Column(DECIMAL(10,4)) #10表示10个阿拉伯数字,4代表4位小数,如100000.1234
    introduction = Column(Text)
    is_delete = Column(Boolean, default=False)
    update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

Base.metadata.create_all()


session = sessionmaker(engine)()

user = User()
user.username = 'heboan'
user.email = '[email protected]'
user.gender = 'm'
user.birthday = datetime.date(2000, 12, 12)
user.height = 1.72
user.wages = 200000.1234
user.introduction = 'hello, my name is heboan....'

session.add(user)
session.commit()
示例

query函数可查询的数据

为了方便演示,我先建一个Student表,然后写入10条数据

...
import random

...
class Student(Base):
    __tablename__ = 'student'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    score = Column(Integer, nullable=False)

    def __repr__(self):
        return '<Student-id:{},name:{},score:{}>'.format(self.id,self.name,self.score)

Base.metadata.create_all()
session = sessionmaker(engine)()

for i in range(1, 11):
    student = Student(name='user{}'.format(i), score=random.randint(20,100))
    session.add(student)
    session.commit()
创建测试数据

1、模型对象。指定查找这个模型的所有对象

result = session.query(Student).all()
print(result)

#[<Student-id:1,name:user1,score:98>, <Student-id:2,name:user2,score:42>,...]

2、模型属性。指定查找某个模型的其中几个属性

result = session.query(Student.id, Student.name).first()
print(result)

#(1, 'user1')

3、聚合函数

  func.count 统计行的数量
  func.avg 求平均值
  func.max 求最大值
  func.min 求最小值
  func.sum 求和

...
from sqlalchemy import func
...

result = session.query(func.avg(Student.score)).all()
print(result)

#[(Decimal('55.6000'),)]
示例

filter方法常用的过滤条件

#查找id为1的记录
article = session.query(Article).filter(Article.id == 1).first()
equal
#查找title不是c++的记录
articles = session.query(Article).filter(Article.title != 'c++').all()
not equal
#查找title以p开头的记录
articles = session.query(Article).filter(Article.title.like('p%')).all()
like
#查找标题不是php也不是java的记录

#方式1:
articles = session.query(Article).filter(Article.title.notin_(['php', 'java'])).all()

#方式2:
articles = session.query(Article).filter(~Article.title.in_(['php', 'java'])).all()
in
#查找detail为空的记录
articles = session.query(Article).filter(Article.detail == None).all()
is null
#查找detail不为空的记录
articles = session.query(Article).filter(Article.detail != None).all()
is not null
//查找title是python并且detail是python111的记录

#方法一:直接使用关键字参数
articles = session.query(Article).filter(Article.title=='python', Article.detail=='python111').all()

#方法二,导入and_方法,from sqlalchemy import and_
articles = session.query(Article).filter(and_(Article.title=='python', Article.detail=='python111')).all()
View Code
#查找title是python,或者detail是python111的记录

导入or_方法, from sqlalchemy import or_
articles = session.query(Article).filter(or_(Article.title=='python', Article.detail=='python111')).all()
or

外键及其四种约束

在MySQL中,外键可以让表之间的关系更加紧密。而SQLAlchemy同样支持外键。通过ForeignKey类来实现。并且可以指定表的外键约束。

from sqlalchemy import ForeignKey

外键的约束有以下几项

  1、RESTRICT : 父表数据被删除,会阻止删除。默认是此项

  2、NO ACTION : 在MySQL中,同RESTRICT一样

  3、CASCADE : 级联删除

  4、SET NULL : 父表被删除,子表数据会设置为NULL(该字段允许为NULL)

...
from sqlalchemy import ForeignKey
...

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)


class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    #注意:这里的uid的数据类型必须要和user表中的id数据类型一致
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))

Base.metadata.create_all()
session = sessionmaker(engine)()

user = User(username='heboan')
session.add(user)
session.commit()

article = Article(title='hello world', content='this is a test line', uid=1)
session.add(article)
session.commit()
示例

ondelete设置外键约束,没有设置该属性则为RESTRICT, 上面我们设置为了RESTRICT

因为我们设置了外键约束是RESTRICT,所以现在删除父表user中id为1 的记录是会被阻止的

 

ORM层面(代码)删除数据,会无视MySQL级别的外键约束。直接将对应的数据删除,然后将从表中的那个外键设置为NULL,如果想要避免这种行为,应该将从表中的外键的nullable=False

ORM层外键和一对多关系

基于上面的情况,就是有两个表,分别为user 和article。他们两之间存在一个外键关系

需求:根据文章找到用户信息原始的做法如下

article = session.query(Article).first()
uid = article.uid
user = session.query(User).get(uid)
print(user)

 上面的方法不够方便,orm可以这样实现

...
from sqlalchemy.orm import relationship
...

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))
    author = relationship("User")  #添加作者属性,通过relationship指向user模型表


article = session.query(Article).first()
print(article.author)
orm通过实现relationship外键查找

我们也可以通过user 来找到该作者发表的文章,一个作者可能有发表多篇文章,这是一对多关系。同样的也是relationship实现

...
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    articles = relationship('Article')
View Code
user = session.query(User).get(2)
for article in user.articles:
    print(article.title)

上面我们两个模型都互相做了relationship实现,这样比较麻烦,还有一种更简便的方法

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)


class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))
    author = relationship("User", backref="articles" ) #这样user模型表就自动拥有了articles属性关联
反向映射

有了上面的关系后,我们添加文章就可以这样

...
user = session.query(User).get(2)
article1 = Article(title='aaa', content='11aa')
article1.author = user

session.add(article1)
session.commit()
添加文章方法1
...
user = session.query(User).get(2)
article1 = Article(title='bb', content='11bb')
user.articles.append(article1)

session.add(user)
session.commit()
添加文章方法2

一对一关系

 举个例子,把user表一些不常用查询的字段放到另外一种表user_extent,这种情况下,这两张表就是一对一的关系

如果想要将两个模型映射成一对一的关系,那么应该在父模型中,指定引用的时候,需要传递一个‘uselist=False’参数,就是告诉父模型,以后引用这个从模型的时候,不再是一个列表,而是一个对象

from sqlalchemy.orm import backref
...

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)

class User_Extent(Base):
    __tablename__ = 'user_extent'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(ForeignKey('user.id'))
    user = relationship('User', backref=backref('extend', uselist=False))
示例

一对一关系映射后,当我们把一个用户设置2个学校就会失败

因此只能一个user只能对应user_extend中的一条记录

多对多关系

场景: 一遍文章可以有多个标签,一个标签可以对应多篇文章

运行后,可以发现数据库成功创建了三张表

我们可以看看创建表的语句

现在我们可以写入数据

...
article1 = Article(title='Python开发')
article2 = Article(title='Java进阶')

tag1 = Tag(name='编程')
tag2 = Tag(name='计算机')

article1.tags.append(tag1)
article1.tags.append(tag2)

article2.tags.append(tag1)
article2.tags.append(tag2)

session.add_all([article1, article2])
session.commit()
写入数据

...
article = session.query(Article).get(1)
for tag in article.tags:
    print(tag.name)
查询文章id为1(Python开发)的标签有哪些
...
tag = session.query(Tag).get(1)
for article in tag.articles:
    print(article.title)
查询标签是'编程'的文章有哪些

relationship方法中的cascade参数详解

如果将数据库的外键设置为RESTRICT,那么在ORM层面,删除了父表中的数据,那么从表中的数据将会NULL。如果不想要这种情况发生,那么应该将这个值的nullable=False。

在SQLAlchemy,只要将一个数据添加到session中,和其他相关联的数据可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过relationship的时候,有一个关键字参数cascade可以这是这些属性:

  1、save-update:默认选项。在添加一条数据的时候,会把其他和其他关联的数据都添加到数据中

  2、delete: 表示当删除某一个模型的数据的时候,是否也删掉使用relationship和他关联的数据

  3、delete-orphan: 表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在多对多以及多对以上。并且还需要再子模型中的relationship中,增加一个single_parent=True的参数

  4、merge:默认选项。当在使用session.merge合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作

  5、expunge: 移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据中删除

  6、all: 是对save-update, merge, refresh-expire, expunge, delete几种的缩写

排序

order_by:根据表中的某个字段进行排序,如果前面加了一个"-",代表的是降序

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    create_time = Column(DateTime, nullable=False, default=datetime.now)

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()
表结构

插入两条测试数据

现在按照文章发表时间排序

article=session.query(Article).order_by(Article.create_time).all()
print(article)

#结果:[<title:title1,create_time:2018-07-08 10:33:17>, <title:title2,create_time:2018-07-08 10:34:00>]
升序
article=session.query(Article).order_by(Article.create_time.desc()).all()
print(article)

#结果:[<title:title2,create_time:2018-07-08 10:34:00>, <title:title1,create_time:2018-07-08 10:33:17>]
降序

方法二:

#升序
article=session.query(Article).order_by('create_time').all()

#降序
article=session.query(Article).order_by('-create_time').all()
示例

__mapper_args__ : 定表模型的时候确定默认的排序规则

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    create_time = Column(DateTime, nullable=False, default=datetime.now)
    __mapper_args__ = {
        "order_by": create_time.desc()   #根据发表时间降序
    }

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()

...


article=session.query(Article).all()
print(article)

#结果
[<title:title2,create_time:2018-07-08 10:34:00>, <title:title1,create_time:2018-07-08 10:33:17>]
定义默认排序规则

limit、offset以及切片操作

...

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    create_time = Column(DateTime, nullable=False, default=datetime.now)

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()



Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()

#写入100条测试数据
for x in range(1,101):
    time.sleep(2)
    title = "title {}".format(x)
    article = Article(title=title)
    session.add(article)
    session.commit()
创建表并写入测试数据

limit:可以限制每次查询的时候只查询几条数据

#查询出全部的数据
articles = session.query(Article).all()
print(articles)


#通过limit查出10条
articles = session.query(Article).limit(10).all()
print(articles)
limit
articles = session.query(Article).order_by(Article.create_time.desc()).limit(10).all()
print(articles)
查出最近发表的10篇文章

offset: 可以限制查找数据的时候过滤前面多少条

#从第5条后面开始查询出10条数据
articles = session.query(Article).offset(5).limit(10).all()
print(articles)


#结果
[<title:title 6,create_time:2018-07-08 12:32:03>,...<title:title 15,create_time:2018-07-08 12:32:21>]
offset

切片: 可以对Query对象使用切片操作,来获取想要的数据

#切片有两种方式:

#需求:找出最近发表的10篇文章

#1、是slice(start,end)
articles = session.query(Article).order_by(Article.create_time.desc()).slice(0, 10).all()
print(articles)

#2、和列表一样的操作
articles = session.query(Article).order_by(Article.create_time.desc())[0:10]
print(articles)
切片

数据查询懒加载技术

 在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以获取全部了。比如有一个作者,想要获取这个作者的所有文章,那么可以通过user.articles就可以。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那这时候我们就可以给relationship传递一个lazy='dynamic',以后通过user.articles获取到的就不是一个列表,而是一个AppendQuery对象了。这样就可以对这个对象再进行一层过滤和排序等操作

lazy可用的选项:

1、'select':这个是默认选选项,比如‘user.articles’的例子,如果你没有访问‘user.articles’这个属性,那么sqlalchemy就不会从数据库中查找文章。一旦你访问了这个属性,那么sqlalchemy就会立马从数据库中查找所有的文章。这也是懒加载。

2、‘dynamic’:就是在访问‘user.articles’的时候返回的不是一个列表,而是一个AppendQuery对象

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)

    def __str__(self):
        return self.username

    def __repr__(self):
        return self.__str__()


class Article(Base):
    __tablename__ = 'article'
    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('user.id'), nullable=False)

    author = relationship(User, backref=backref('articles', lazy='dynamic'))   #lazy='dynamic'

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()



Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()

#写入测试数据
user = User(username='heboan')
session.add(user)
session.commit()

user = session.query(User).first()

for x in range(1,101):
    time.sleep(2)
    title = "title {}".format(x)
    article = Article(title=title)
    article.author = user
    session.add(article)
    session.commit()
dynamic

上面配置了lazy='dynamic',并写入了测试数据,我们可以看看‘user.articles’类型为AppendQuery

...
user = session.query(User).first()
print(type(user.articles))


#结果
<class 'sqlalchemy.orm.dynamic.AppenderQuery'>
示例

既然user.articles为AppendQuery类型,那么它就有query拥有的属性,比如(filter、order_by等等),还可以append数据,如下

...
user = session.query(User).first()
# filter
print(user.articles.filter(Article.create_time == '2018-07-08 16:55:05').all())
#结果: [<title:title 2,create_time:2018-07-08 16:55:05>]

#append
article = Article(title='python is simple')
user.articles.append(article)
session.commit()
示例

group_by和having子句

猜你喜欢

转载自www.cnblogs.com/sellsa/p/9248870.html
今日推荐