对象关系映射:(Object Relational Mapping)简称ORM;是一种程序设计技术;用于实现面向对象编程语言里不同类型系统的数据之间的转换
从效果来看;它创造了一个可在编程语言里使用的“虚拟对象数据库”
大白话;对象模型与数据库表的映射
随着项目越来越大;采用写原生SQL的方式在代码中会出现大量的重复SQL语句;那么;问题就出现了
SQL语句重复利用率不高;越复杂的SQL语句条件越多;代码越长;会出现很多相近的sql语句很多SQL语句是在业务中拼接出来的;如果数据库需要更改;就要修改这些逻辑;很容易漏掉某些SQL的修改写SQL时容易忽略web安全问题;造成隐患ORM可以通过类的方式去操作数据库;而不用再写原生的数据库语句;通过把表映射成类;把行作为实例;把字段作为属性;ORM在执行对象的时候最终还是会把对象的操作转化为数据库的原生语句;使用ORM有许多优点
易用性;使用ORM做数据库开发可以减少重复SQL语句的概率;写出的模型更加直观、清晰性能消耗少;ORM转换成底层数据库操作指令确实会有一些开销;但从实际情况来看;这种损耗很少;不足5%;只要不是针对性能有严苛的要求;综合考虑开发效率、代码阅读性;带来的好处远大于性能损耗;而且项目越大作用月明显。设计灵活;可以轻松写出复杂的查询可移植性;SQLAlchemy封装了地层的数据库实现;支持多个关系数据库引擎;包括流行的mysql;PostgreSQL等;可以非常轻松切换数据库。from sqlalchemy import create_engine
# 数据库变量
HOST = ;localhost;
PORT = 3306
DATA_BASE = ;flask_db;
USER = ;root;
PWD = ;root;
# mysql;驱动名;//用户名;密码;地址;端口/数据库名
DB_URI = f;mysql;pymysql://{USER}:{PWD};{HOST}:{PORT}/{DATA_BASE};
engine = create_engine(DB_URI)
# 执行一个SQL;虚拟;
sql = ;select 1;
# 连接数据库
conn = engine.connect()engine = create_engine(DB_URI)
sql = ;create table t_user(id int primary key auto_increment,name varchar(32));;
conn = engine.connect()
conn.execute(sql)
rs = conn.execute(sql)
print(rs.fetchone())
engine = create_engine(DB_URI)
sql = ;create table t_user(id int primary key auto_increment,name varchar(32));;
conn = engine.connect()
conn.execute(sql)
为了数据安全;可以使用with语句
engine = create_engine(DB_URI)
sql = ;create table t_user1(id int primary key auto_increment,name varchar(32));;
with engine.connect() as conn:
conn.execute(sql)
from sqlalchemy import create_engine,Column,Integer,String
from sqlalchemy.ext.declarative import declarative_base
HOST = ;localhost;
PORT = 3306
DATA_BASE = ;flask_db;
USER = ;root;
PWD = ;root;
DB_URI = f;mysql;pymysql://{USER}:{PWD};{HOST}:{PORT}/{DATA_BASE};
engine = create_engine(DB_URI)
# 创建一个基础类
Base = declarative_base(engine)
class Person(Base):
__tablename__ = ;t_person;
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
age = Column(Integer)
country = Column(String(32))
# 映射表结构
Base.metadata.create_all()
注意
一旦使用Base.metadata.create_all()将模型映射到数据库后;即使改变了模型的字段;也不会重新映射了
所有和数据库的ORM操作都必须通过session的会话对象进行实现
from sqlalchemy import create_engine,Column,Integer,String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
HOST = ;localhost;
PORT = 3306
DATA_BASE = ;flask_db;
USER = ;root;
PWD = ;root;
DB_URI = f;mysql;pymysql://{USER}:{PWD};{HOST}:{PORT}/{DATA_BASE};
engine = create_engine(DB_URI)
# 创建一个基础类
Base = declarative_base(engine)
class Person(Base):
__tablename__ = ;t_person;
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
age = Column(Integer)
country = Column(String(32))
from sqlalchemy.orm import sessionmaker
# 创建session对象
Session = sessionmaker(engine)
def create_data_one():# 增加一条数据
with Session() as session: # 如果这里报错__enter__;请按照新版本sqlalchemy
p1 = Person(name=;张三;,age=18,country=;China;)
session.add(p1)
session.commit()
def create_data_all(): # 增加多条数据
p1 = Person(name=;李四;,age=19,country=;America;)
p2 = Person(name=;王五;,age=23,country=;England;)
with Session() as session:
session.add_all([p1,p2])
session.commit()
def query_data(): # 查询多条数据;返回姓名
with Session() as session:
all_person = session.query(Person).all()
for person in all_person:
print(person.name)
def query_data_one(): # 查询第一条数据(第一条数据中的年龄)
with Session() as session:
person = session.query(Person).first()
print(person.age)
def query_data_by_params(): # 增加过滤条件进行查询
with Session() as session:
# 如果有一条用first;有多条用all
# p1 = session.query(Person).filter_by(name=;李四;).first()或
p1 = session.query(Person).filter(Person.name==;李四;).first() # Person.name是一个属性;所以要用判断运算符;而不是赋值
print(p1.country)
def update_data(): #修改数据
with Session() as session:
p1 = session.query(Person).filter_by(name=;张三;).first() # 先查询语句
p1.age = 30
session.commit() # 修改后一定要提交
def delete_data(): # 删除数据
with Session() as session:
p1 = session.query(Person).filter(Person.name==;李四;).first()
session.delete(p1)
session.commit()
def delete_data_all():
with Session() as session:
all_datas = session.query(Person).all()
for data in all_datas:
session.delete(data)
session.commit()
if __name__ == ;__main__;:
# create_data_one()
# create_data_all()
# query_data()
# query_data_one()
# query_data_by_params()
# update_data()
# delete_data()
delete_data_all()
字段中设置参数;完成特殊功能
primary_key:True设置某个字段为主键autocrement:True设置字段自增default设置某个字段为默认值unllable指定某个字段是否为空unique指定某个字段是否唯一;默认Falseonupdate在数据更新的时候会调用这个参数指定的值或函数from datetime import datetime
from db_utils import * # 配置的mysql文件;之际调用
from sqlalchemy import Column,Integer,String,DateTime
class News(Base):
__tablename__ = ;t_news;
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False) # 不允许为空
read_count = Column(Integer,default=1) # 阅读量
create_time = Column(DateTime,default=datetime.now) # 创建时间
update_time = Column(DateTime,default=datetime.now,onupdate=datetime.now) # 更新时间
phone = Column(String(11),unique=True) # 账号唯一
def create_date():
new1 = News(phone=;18411111111;,title=;测试列参数;)
with Session() as session: # 如果这里报错__enter__;升级sqlalchemy
session.add(new1)
session.commit()
if __name__ == ;__main__;:
# Base.metadata.create_all()# 映射表结构
create_date()
对数据进行过滤
query函数中有可以传递三种类型的数据
模型名;指定查找这个模型中所有属性;上文已经略微提到过;模型中的属性;可以只查找某个模型中指定的几个属性聚合函数:func.count,func.avg,func.max,func.min,func.sum等func上;其实没有任何聚合函数;但是因为它做了一些魔术;只要mysql中有的聚合函数;都可以通过func调用
from db_utils import *
from random import randint
from sqlalchemy import Integer,String,Column,func
class Item(Base):
__tablename__ = ;t_item;
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32))
price = Column(Integer)
def create_data():
with Session() as sessin:
for i in range(10): # 随机生成10条数据
item = Item(title = f;产品{i;1};,price=randint(1,100))
sessin.add(item)
sessin.commit()
def query_model_name():# 通过类名查询
with Session() as session:
rs = session.query(Item).all()
for r in rs:
print(r.price)
def query_model_attr():# 通过属性获取指定字段查询
with Session() as session:
rs = session.query(Item.title,Item.price,Item.id).all()
for r in rs:
print(f;产品ID;{r.id}-产品名;{r.title}-产品价格;{r.price};)
def query_by_func():
with Session() as session:
count = session.query(func.count(Item.id)).first() #查询数据的数量
price_max = session.query(func.max(Item.price)).first()
print(f;有{count}条数据,最高价格是{price_max};)
if __name__ == ;__main__;:
# Base.metadata.create_all()
# create_data()
# query_model_name()
# query_model_attr()
query_by_func()
过滤数据是一个很重要的功能;以下是一些常用的过滤条件;并且这些过滤条件都只能通过filter来实现
equals:== 精准匹配not equals;;= 补匹配like/ilike;不区分大小写in;在某个范围内not in;不在某个范围内is null;为空is not null;不为空and;条件拼接or;多条件满足其一的from sqlalchemy import Column,String,Integer,Float,Text
from random import randint
from db_utils import *
from uuid import uuid4
class Article(Base):
__tablename__ = ;t_article;
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False)
price = Column(Float,nullable=False)
content = Column(Text)
def __repr__(self):
# 一个魔术方法;可以直接打印字符串而不是地址
return f;<Articla(title:{self.title} price:{self.price} content:{self.content})>;
# 先创建一批数据
def create_data():
with Session() as session:
for i in range(20):
if i%2 == 0:
art = Article(title = f;title{i;1};,price=randint(1,100),content=uuid4())
else:
art = Article(title=f;TITLE{i;1};,price = randint(1,100))
session.add(art)
session.commit()
def query_data():
with Session() as session:
# rs = session.query(Article).filter_by(id=1).first()或
rs = session.query(Article).filter(Article.id==1).first() # 推荐使用
print(rs)
def query_data_equal():
with Session() as session:
rs = session.query(Article).filter(Article.title==;title2;).first() # 推荐使用
print(rs)
def query_data_equal_not():
with Session() as session:
rs = session.query(Article).filter(Article.title!=;title2;).all() # 推荐使用
print(rs)
def query_data_like():
# 模糊匹配
with Session() as session:
# select * from t_article where title like ;titl%
rs = session.query(Article).filter(Article.title.like(%title%)).all() # 推荐使用
for r in rs:
print(r)
def query_data_in():
# 模糊匹配
with Session() as session:
# 这里的in_是固定用法;为了避免Python中关键字的冲突
rs = session.query(Article).filter(Article.title.in_([;title1;,;title2;,;title3;,;title5;,;title6;])).all() # 推荐使用
for r in rs:
print(r)
if __name__ == ;__main__;:
# Base.metadata.create_all()
# create_data()
# query_data()
# query_data_like()
query_data_in()
表之间存在三种关系;一对一;一对多;多对多
在sqlalchemy中的ORM可以模拟这三种关系
因为一对一再sqlalchemy底层中是通过一对多的方式模拟的;所以先看一下一对多的关系;
外键
使用sqlalchemy创建外键非常简单。从表中增加一个字段;指定这个字段外键是哪个表的哪个字段就可以。从表中外键的字段;必须和主表的主键保持一致。使用Foreignkey关键字。
sqlalchemy提供了一个relationship;这个类可以定义属性;以后在访问关联表的时候就可以直接通过属性访问的方式来访问。
另外 ;可以通过backref来指定反向访问的属性名称。
from db_utils import Session,Base
from sqlalchemy import String,Column,Integer,Text,ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = ;t_user;
id = Column(Integer,primary_key=True,autoincrement =True)
uname = Column(String(50),nullable=False,name=;name;)
def __repr__(self):
return f;id={self.id} uname={self.uname};
# 一对多 ForeignKey放在多的那一方
class News(Base):
__tablename__ = ;t_news;
id = Column(Integer,primary_key=True,autoincrement =True)
title = Column(String(32),nullable=False)
content = Column(Text,nullable=False)
uid = Column(Integer,ForeignKey(;t_user.id;))
user = relationship(;User;) # 将两张表联系起来;这里是类名而不是表名
def __repr__(self):
return f;News:id={self.id} content={self.content} uid={self.uid} title={self.title};
def create_data():
user = User(uname=;zs;)
news1 = News(title=;Python;,content=;Flask;,uid=1)
news2 = News(title=;Java;,content=;Spring;,uid=1)
with Session() as session:
session.add(user)
session.commit()
with Session() as session:
session.add_all([news1,news2])
session.commit()
def query_data():
with Session() as session:
# select u.id u.uname from t_news left join t_user u n.uid = u.id where n.id=1;
news1 = session.query(News).first()
print(news1.user) # News表中user通过relationship与User连接;
if __name__ == ;__main__;:
# Base.metadata.create_all()
# create_data()
query_data()
在sqlalchemy中;如果想要将两个模型映射成一对一的关系;那么应该在父模型中;指定引用的时候;要传递一个uselist=False这个参数进去。就是告诉父模型;以后引用这个从模型时;不再是一个列表而是一个对象了。
from db_utils import Session,Base
from sqlalchemy import Column, ForeignKey,String,Integer
from sqlalchemy.orm import relationship
class LoginUser(Base):
__tablename__ = ;t_user_login;
id = Column(Integer,primary_key=True,autoincrement=True)
uname=Column(String(32),nullable=False)
passwd = Column(String(32),nullable=False)
user = relationship(;User;,uselist=False) # 要在父表中传递uselist=False
def __repr__(self):
return f;LoginUser:id={self.id} uname={self.uname} passwd={self.passwd};
# 创建一对一关系;要创建一个字段作为表的表示;外键;
class User(Base):
__tablename__ = ;t_user;
id = Column(Integer,primary_key = True,autoincrement=True)
name = Column(String(32),nullable=False,name=;name;)
gender = Column(String(1))
address = Column(String(64))
login_id = Column(Integer,ForeignKey(;t_user_login.id;))
def __repr__(self):
return f;User id={self.id} name={self.name} gender={self.gender} address={self.address};
def create_data():
login = LoginUser(uname=;zs;,passwd=;123;)
user = User(name=;张三;,gender=;男;,address=;保密;)
login.user = user
with Session() as session:
session.add(login)
session.commit()
def query_data():
with Session() as session:
login = session.query(LoginUser).first()
print(login.user)
if __name__ == ;__main__;:
# Base.metadata.create_all()
# create_data()
query_data()
每类产品有很多人购买;一个人可以选购多种商品
多对多的关系需要一张中间表来绑定它们之间的关系先把两个模型定义出来;再使用Table定义一个中间表;中间表一般包括两个模型的外键字段就可以了;并且让它们作为一个联合主键在两个需要做多对多的模型中随便选一个模型;定义一个relationship属性;来绑定这三者的关系;在使用relationship时;需要传入一个secondary=中间表对象名from db_utils import Session,Base
from sqlalchemy.orm import relationship
from sqlalchemy import Column,String,Integer,ForeignKey,Table
;;;
一个新闻可以拥有多个标签;
一个标签可以对应多个新闻。
;;;
# 第三张表;要放在两个模型的上边;用来关联两个模型
news_tag = Table(
;t_news_tag;,
Base.metadata,
Column(;news_id;,Integer,ForeignKey(;t_news.id;),primary_key=True),
Column(;tag_id;,Integer,ForeignKey(;t_tag.id;),primary_key=True)
)
class New(Base):
__tablename__ = ;t_news;
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False)
tags = relationship(;Tag;,backref=;newss;,secondary=news_tag) # 第三张表模型名而不是表名;也可以写另一张表中
def __repr__(self):
return f;New: id={self.id} title={self.title};
class Tag(Base):
__tablename__ = ;t_tag;
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32),nullable=False)
def __repr__(self):
return f;Tag id={self.id} name={self.name};
def create_data():
new1 = New(title=;这个是标题1;)
new2 = New(title=;这个是标题2;)
tag1 = Tag(name=;标签1;)
tag2 = Tag(name=;标签2;)
new1.tags.append(tag1)
new1.tags.append(tag2)
new2.tags.append(tag1)
new2.tags.append(tag2)
with Session() as session:
session.add(new1)
session.add(new2)
session.commit()
def query_data():
with Session() as session:
new = session.query(New).first()
print(new.tags)
if __name__ == ;__main__;:
# Base.metadata.create_all()
# create_data()
query_data()
ORM层面的relationship方法中的cascade
sqlalchemy中;只要将一条数据添加到session中;和它关联的数据都可以一起存入到数据库中。
这些是怎么设置的呢。其实是通过relationship的时候;有一个关键字参数cascade可以设置这些属性。
cascade属性值为;
save-updatae;默认选项。在添加一条数据的时候;会把其他和它相关的数据都添加到数据库中。这种行为就是save-update影响的。delete;表示当删除某一个模型中的数据时;是否也删除使用relationship和它关联的数据。delete-orphan:表示当一个对ORM解除了父表中的关系对象的时候;自己便会被删除掉。merge;默认选项;当使用session.merge合并一个对象时;会将使用了relationship相关联的对象也进行merge操作。expunge;移除操作时;会将相关联的对象也进行移除;这个操作只能从session中移除;并不会真正从数据库中删除。all;是对save-update;merge;refresh-expire;expunge;delete几种缩写。from unicodedata import name
from db_utils import Session,Base
from sqlalchemy import Column,String,Integer,ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = ;t_user;
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
def __repr__(self):
return f;User:id={self.id} name={self.name};
class Article(Base):
__tablename__ = ;t_article;
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32))
uid = Column(Integer,ForeignKey(;t_user.id;))
user = relationship(;User;,backref=;articles;,cascade=;save-update;)
def create_data():
Base.metadata.drop_all() # 删现有有表
Base.metadata.create_all()
# 初始化数据
user = User(name=;张三;)
art1 = Article(title=;这个是标题一;,uid=1)
art2 = Article(title=;这个是标题二;,uid=1)
user.articles.append(art1)
user.articles.append(art2)
with Session() as session:
session.add(user)
session.commit()
if __name__ == ;__main__;:
create_data()
flask-sqlalchemy是一个插件;是对sqlalchemy进行了一个简单的封装;使我们在flask中使用sqlalchemy更加简单。
pip install flask-sqlalchemy
数据库初始化不再使用create_engine
与sqlalchemy一样;定义好数据库连接字符串DB_URI
将这个定义好的数据库通过SQLALCHEMY_DATABASE_URI配置到app.config中
app.config[;SQLALCHEMY_DATABASE_URI;]=DB_URI
使用flask_sqlalchemy.SQLAlchemy 这个类定义一个对象,并将app传进去
db=SQLAlchemy(app)
之前都是通过Base=declarative_base()来初始化一个基类;然后再继承。而在flask-sqlalchemy中更加简单了
还是跟使用sqlalchemy一样;定义模型类。现在不需要使用declarative_base()创建了。而是使用db_Model来作为基类。在模型类中;Column;String;Integer;relationship都不需要再导入了;直接使用db下的属性名即可;db.Integer;在定义模型的时候;可以不用写__tablename__;flask会默认使用当前模型的名字;转为小写来作为表的名字。并且如果这个模型使用了多个单词并使用了驼峰命名法;那么会在多个单词之间增加下划线来进行连接;但是不推荐使用;
写完模型后;要将模型映射到数据库表中;使用以下代码;
删除数据库表db.drop_all()创建数据库表db.create_all()在flask-sqlalchemy中session不再使用sessionmaker来创建了;直接使用db.session即可;操作这个session和之前的是一样的
添加数据个之前没有区别;只是session成为了一个db属性
单表查询;数据查询不再是之前的session.query(),而是将query属性放在了db.Model上;所以查询就是通过模型名.query的方法查询了;query与之前的用法一样
多表查询
如果查找数据涉及了多个模型;只能使用db.session.query(模型名).all()这种方法
与之前没有区别;只是session成为了db的一个属性
与之前没有区别;只是session成为了db的一个属性
from operator import ne
from flask import Flask, session
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 数据库变量
HOST = ;localhost;
PORT = 3306
DATA_BASE = ;flask_db;
USER = ;root;
PWD = ;root;
# mysql;驱动名;//用户名;密码;地址;端口/数据库名
DB_URI = f;mysql;pymysql://{USER}:{PWD};{HOST}:{PORT}/{DATA_BASE};
app.config[;SQLALCHEMY_DATABASE_URI;] = DB_URI
app.config[;SQLALCHEMY_TRACK_MODIFICATIONS;] = False # 如果不加这个;会报警告;不是报错;
# 连接数据库
db = SQLAlchemy(app)
# 创建模型
class User(db.Model):
__tablename__ = ;t_user; # 这个可以不用;但是推荐使用
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(32))
def __repr__(self):
return f;User:id={self.id},name={self.name};
class News(db.Model):
__tablename__ = ;t_news; # 这个可以不用;但是推荐使用
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
content = db.Column(db.String(256))
uid = db.Column(db.Integer,db.ForeignKey(;t_user.id;))
user = db.relationship(;User;,backref=;news;)
def __repr__(self):
return f;News:id={self.id},content={self.content},uid={self.uid};
# 删除表
# db.drop_all()
# 创建表
# db.create_all()
# 增加数据
def create_data():
user = User(name=;张三;)
news=News(content=;这个是张三写的内容;)
# 建立关联关系
user.news.append(news)
# 添加到session
db.session.add(user)
db.session.commit()
# 查询单表数据
def query_data_one():
users = User.query.all()
print(users)
# 查询多表数据
def query_data_many():
rs = db.session.query(User,News.content).join(News,News.uid == User.id).all()
print(rs)
# 修改数据
def update_data():
user = User.query.first()
user.name = ;李四;
db.session.commit()
# 删除数据
def delete_data():
news = News.query.first()
db.session.delete(news)
db.session.commit()
if __name__ == ;__main__;:
# create_data()
# query_data_one()
# query_data_many()
# update_data()
delete_data()
alembic是一个数据库迁移工具;使用来做ORM模型与数据库的迁移与映射
alembic使用与git有点类似
alembic命令都是以almbic开始的
pip install alembic
# alembic.py
from sqlalchemy import create_engine,Column,String,Integer
from sqlalchemy.ext.declarative import declarative_base
# 数据库信息
HOST = ;localhost;
PORT = 3306
DATA_BASE = ;flask_db;
USER = ;root;
PWD = ;root;
# mysql;驱动名;//用户名;密码;地址;端口/数据库名
DB_URI = f;mysql;pymysql://{USER}:{PWD};{HOST}:{PORT}/{DATA_BASE};
engine = create_engine(DB_URI)
Base = declarative_base(engine)
class User(Base):
__tablename__ = ;t_user;
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
cd 当前文件夹下
alembic init 取一个文件夹名字
命令输完后;会生成一个文件夹;文件夹的名字就是 init 后面的名字
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__name__)))
import alembic_demo
target_metadata = alembic_demo.Base.metadata
进入命令行
alembic reVision --autogenerate -m ;model_file;
--autogenerate 自动更新
-m ;model_file; 增加提示信息
执行完命令后;会在versions文件夹生成一个迁移文件df935ec6b6c2_model_file.py;前面的字母数字是版本号df935ec6b6c2
再次之前;数据库中虽然会出现一张版本信息表;但是表结构还未映射;表中没有信息;只有在映射了表结构之后;版本号才会
出现在信息表中。
命令行中
alembic upgrade head
操作大多都一样
target_metadata = app_demo.db.Model.metadata