ORM、SQLAlchemy数据库操作

ORM介绍

背景:
用底层的sql写的话,相当于通过pymysql 游标的方式连接“http://blog.51cto.com/jacksoner/2113454 ”,为了避免把sql语句写死在代码里,有没有一种方法直接把原生sql封装好了并且以你熟悉的方式操作,像面向对象那样?

ORM就是对象映射关系程序。相当于ORM帮我们SQL写成类的形式,然后通过类来调用,获取,而不是写底层的sql(insert,update,select)来获取。

ORM 相当于把数据库也给你实例化了,在代码操作mysql中级又加了orm这一层。

orm的优点:
1、隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
2、ORM使我们构造固化数据结构变得简单易行。

sqlalchemy安装

在Python中,最有名的ORM框架是SQLAlchemy。用户包括openstack\Dropbox等知名公司或应用

安装:

pip install SQLAlchemy
SQLAlchemy==1.2.7

pip install pymysql

数据库:

CentOS Linux release 7.4.1708

mysql版本:5.6.35
IP:192.168.1.48
用户:root
密码:123456
创建一个超级用户:grant all privileges on . to jacker@’%’ identified by ‘123456’ WITH GRANT OPTION;
创建一个数据库: mysql> create database sqlalchemy;

方式一:

import pymysql
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://jacker:[email protected]/sqlalchemy',encoding='utf-8',echo=True)
#echo=True:显示创建sql信息
Base = declarative_base()  # 生成orm基类

#这里的继承Base的类,创建user表,字段id(整型,主键) name(列 字符串32位) 密码(列 字符串64位)
class User(Base):
    __tablename__ = 'user'  # 表名
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    password = Column(String(64))
Base.metadata.create_all(engine) #创建表结构 (这里是父类调子类)

运行结果:

未分类

方式二 创建表,并写入数据:

创建表:

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String

engine = create_engine('mysql+pymysql://jacker:[email protected]/sqlalchemy',encoding='utf-8')
metadata = MetaData(engine)

teacher = Table('user', metadata,
            Column('id', Integer, primary_key=True),
            Column('name', String(50), ),
            Column('fullname', Integer),
            Column('password', String(10)),
)
metadata.create_all(engine)

写入数据:

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

engine = create_engine('mysql+pymysql://jacker:[email protected]/sqlalchemy',encoding='utf-8')
#echo=True:显示信息
DBsession = sessionmaker(bind=engine)# 实例和engine绑定
session = DBsession()                 # 生成session实例,相当于游标

Base = declarative_base()# 生成orm基类

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(100))
    fullname = Column(String(100))
    password = Column(String(100))

#实例化操作
user1 = User(id=1001, name='ling', password=123)
user2 = User(id=1002, name='molin', password="123abc")
user3 = User(id=1003, name='karl', password=16)

#需要添加到session中,然后再提交,表里才可以有数据
session.add(user1)
session.add(user2)
session.add(user3)
session.commit()

验证:

未分类

数据查询

my_user=session.query(User).filter_by(name='molin').first()#显示匹配name=molin的第一条数据,因为name='molin' 可能有多个值
my = session.query(User).filter_by().all()                  #显示所有
print(my)                                                   #这个是个列表,通过循环遍历
for i in my:
    print(i.name)
    print(i.password)
print(my_user.id,my_user.password,my_user.name)             #调用
#session.commit()                                          #查询就不用提交了
session.close()
'''
filter和filter_by   条件查询

filter_by(name="ling")  不能使用>  <  =
filter(Student.id>1001)  这个就必须使用Student.id  可以使用> < =等
'''
a = session.query(User).filter(User.id>1002).all()
for i in a:
    print(i.name)
    print(i.password)
session.close()
如果出现name相同的,想都打印出来的话:
#实例化操作
user1 = User(id=1001, name='test1', password=123)
user2 = User(id=1002, name='test2', password="123abc")
user4 = User(id=1004, name='test2', password="123abc")
user3 = User(id=1003, name='test3', password=16)

my_user = session.query(User).filter_by(name="test2").all()       #my_user:这个是列表
for i in my_user:
    print(i.id)
    print(i.name)
    print(i.password)
多条件查询

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

上面2个filter的关系相当于 user.id >1 AND user.id <7 的效果

修改数据

my_user = session.query(User).filter_by(name="molin").first()
my_user.name = "fenggf" # 查询出来之后直接赋值修改
my_user.passwork = "123qwe"
session.commit()

[Flask教程] 7.ORM与SQLAlchemy (3) – flask-migrate数据库迁移

在上一遍文章中,我们增加了两个模型Questions和Comments,并为Users增加了avatar_path这个字段,然后通过这段代码更新到数据库:

with app.test_request_context():
    db.drop_all()
    db.create_all()

因为当使用过db.create_all()之后,再次直接使用db.create_all(),对模型的修改并不会更新到数据库,我们要使用db.drop_all()先把数据库中所有的表先删除掉,然后再db.create_all()一次。听上去是不是很麻烦?更糟糕的是,原先数据库的的数据也就没有了。所以我们不用这种简单粗暴的方式去更新数据库结构,而是借助flask-migrate这个专门用于迁移数据库的工具,它可以在保留数据库原始数据的情况下,完成模型的更新。此外,我们还将结合flask-script一起使用,简单来说flask-script让我们可以使用命令行去完成数据库迁移的操作。

在项目主文件夹下新建一个manage.py,代码如下:

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from HarpQA import app, db
from models import Users, Questions, Comments

manager = Manager(app)

migrate = Migrate(app, db)

manager.add_command('db', MigrateCommand)


if __name__ == '__main__':
    manager.run()

首先导入相关的类,注意模型要全部导入过来,即使代码中并没有显式地使用它们。然后传入app或db来构建Manager和Migrate两个类的实例,最后将MigrateCommand的命令加入到manager中。

此时我们假设要更新模型的结构,在models.py的User模型结尾添加一行代码test = db.Column(db.Integer),然后点击PyCharm下方的Terminal,自动进入到了虚拟环境的命令行中,输入python manage.py db init来初始化,这一步主要是建立数据库迁移相关的文件和文件夹,只是在第一次需要使用。接着依次使用python manage.py db migrate和python manage.py db upgrade,待运行完成,查看users_infor表的结构,结果如下:

未分类

可以看到test字段已经添加到表中了。

[Flask教程] 6.ORM与SQLAlchemy (2) – 模型关系与引用

承接上文,我们的Q&A demo,除了用户表,还需要存储所有问题内容的表questions_info和存储所有评论的表comments_info,并且都和users_info通过外键来关联。我们不排除后续需要更多表的可能性,把所有模型和视图函数写在一起看着也太混乱了!为此,我们新建一个models.py,把三个模型都放在这里。

由于是新建的models.py文件,我们同样要先在开头生成一个名为db的SQLAlchemy对象:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

前文中我们给SQLAlchemy传入了Flask对象app作为参数,这里是不是也要从视图函数文件HarpQA.py导入那个app并传进去呢?并不可以,因为HarpQA.py也要使用到db(如db.session),这样就产生了循环引用,所以在这里不能传入app,而是回到HarpQA.py,使用db.init_app(app)将app和db绑定,避免了循环引用。

users_info表(Users模型)代码如下:

class Users(db.Model):
    __tablename__ = 'users_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(32), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    register_time = db.Column(db.DateTime, nullable=False, default=datetime.now())
    # 我们新增了一个avatar_path字段来存用户头像图片文件的路径
    avatar_path = db.Column(db.String(256), nullable=False, default='images/doraemon.jpg')    

questions_info表(Questions模型)代码如下:

class Questions(db.Model):
    __tablename__ = 'questions_info'
    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)
    author_id = db.Column(db.Integer, db.ForeignKey('users_info.id'))
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.now())

    author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))

这个表存储所有问题的标题、内容、创建时间、作者ID,作者ID通过外键与用户表的ID关联,方式也很简单,在db.Column中用db.ForeignKey(‘users_info.id’)作为参数即可。
再看最后一条语句:

author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))

db.relationship会自动找到两个表的外键,建立Questions和Users的关系,此时对于任意一个Questions对象question,通过question.author就可获得这个question的作者对应的Users对象,例如获取id为1的问题的作者姓名:

question = Questions.query.filter(Questions.id == 1).first()
author_name = question.author.username

db.relationship的第二个参数backref=db.backref(‘questions’, order_by=create_time.desc())则建立了一个反向引用,这样我们不仅可以使用question.author,还可以使用author.questions获得一个作者所有的问题,并通过order_by=create_time.desc()按创建时间倒序排列(网页的内容按时间倒序排列),返回的是一个Questions对象的列表,可以遍历它获取每个对象,如获取作者Harp的所有问题的title:

author = Users.query.filter(Users.username == 'Harp').first()
for question in author.questions:
    print(question.title)

同理,comments_info表(Comments模型)代码如下:

class Comments(db.Model):
    __tablename__ = 'comments_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    content = db.Column(db.TEXT, nullable=False)
    question_id = db.Column(db.Integer, db.ForeignKey('questions_info.id'))
    author_id = db.Column(db.Integer, db.ForeignKey('users_info.id'))
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.now())

    author = db.relationship('Users', backref=db.backref('comments'))
    question = db.relationship('Questions', backref=db.backref('comments', order_by=create_time.desc()))

在HarpQA.py中,我们要从models.py导入db及所有的模型,注意因为上下文的关系,我们这里用with语句把app推入栈中:

from flask import Flask, render_template
from models import db, Users, Questions, Comments

import config

app = Flask(__name__)
app.config.from_object(config)

db.init_app(app)

with app.test_request_context():
    db.drop_all()
    db.create_all()


@app.route('/')
def index():
    return render_template('home.html')


if __name__ == '__main__':
    app.run()

运行脚本,此时数据库已经把三张表都建立好了:

未分类

[Flask教程] 5.ORM与SQLAlchemy (1) – 建立第一个模型

后端一个重要的点就是与数据库联系,例如网页的注册、登录,内容的更新等都需要与数据库建立关系。以MySQL数据库为例,平时我们会用mysqldb(python 2)或者pymysql(python 3)去操作MySQL数据库,但这种方法也是需要自己编写SQL语句的。现在我们有了ORM模型,简单来说,ORM是把数据库中的表抽象成模型,表的列名对应模型的属性,这样我们可以调用类的属性或方法去获得数据库中的数据。例如假设MySQL数据库中有一张表名为table1,使用SELECT * FROM table1 WHERE id=1获取id为1的数据,如果将表table1映射成ORM模型Table,那么可以直接使用Table.query.filter(id=1),这样操作简单了很多,也很利于理解。

SQLAlchemy就是一个这样的ORM,我们可以直接安装flask_sqlalchemy来使用。在这之前我们先在MySQL中手动建立一个数据库harp,在建立的时候把charset设置为utf8,避免存入中文时变成乱码,然后在配置文件config.py中填写好数据库的连接信息:

HOST = "127.0.0.1"
PORT = "3306"
DB = "harp"
USER = "root"
PASS = "Your Password"
CHARSET = "utf8"
DB_URI = "mysql+pymysql://{}:{}@{}:{}/{}?charset={}".format(USER, PASS, HOST, PORT, DB, CHARSET)
SQLALCHEMY_DATABASE_URI = DB_URI

SQLAlchemy依赖mysqldb或者pymysql去连接数据库和执行SQL语句,因为我们用的是python 3,所以需要在配置信息中指明使用pymysql,如果是python 2可以省略,默认是使用mysqldb。

建立好了数据库,我们开始建表,首先建立一张用户表,我们设想它应该有id(作为主键)、用户名、密码、注册时间这些基本的字段,有了ORM,我们就不用再写SQL去建表了,在项目的主py文件中添加以下代码:

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

import config

app = Flask(__name__)
app.config.from_object(config)

db = SQLAlchemy(app)


class Users(db.Model):
    __tablename__ = 'users_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(32), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    register_time = db.Column(db.DateTime, nullable=False, default=datetime.now())


db.create_all()

解读一下这段代码,导入SQLAlchemy和含有数据库连接信息的config,实例化一个SQLAlchemy对象名为db,其传入的参数为Flask实例app。接下来定义了一个User类,这个类就是ORM中的模型,也就是数据库中的表映射的模型,它需要继承自db.Model,tablename这个属性就是建表后,数据库生成的表名;然后使用db.Column来实例化id/username/password/register_time这几个列,db.Column的参数描述列的类型、主键等信息,如db.Integer/db.String(32)/db.DateTime分别代表整形、字符串(最大长度)、时间,primary_key=True说明该字段为主键,autoincrement=True代表自增长,nullable决定是否可为空,default代表默认值。最后用db.create_all()来实现创建。我们暂时不用理解为何SQLAlchemy需要传入Flask实例作为参数,为何模型要继承自db.Model,重要的是可以先把想要的表建立起来。

进入数据库,输入desc user_info;,我们发现表已经建立好了,其结构图如下:

未分类

但它现在还是空的,我们来试着插入一条语句,将视图函数修改为:

@app.route('/')
def index():
    user = Users(username='Harp', password='123456')
    db.session.add(user)
    db.session.commit()
    return render_template('home.html')

代码实例化一个Users的对象user,传入username和password,使用db.session.add(user)将其加入到数据库的session(可以理解为事务)中,然后使用db.session.commit()提交。我们运行程序,然后用浏览器访问,浏览器正常显示了结果,这时再看一眼数据库,发现这条数据已经写入到了数据库:

未分类

查询、修改数据也同样很简单:

@app.route('/')
def index():
    user = Users.query.filter(Users.id == 1).first()    #查找
    print(user.username)
    user.username = 'Harp1207'    #修改
    db.session.commit()    #修改后需提交
    print(user.username)
    return render_template('home.html')

思考问题:

  1. 为何要把模型的操作语句放在视图函数中?(搜索上下文这个概念)
  2. 数据查找,我们用的是Model.query,其实还可以用db.session.query,两者有何区别?filter和filter_by又有何区别?