8.1. 一对多
.1简介
设计数据库表,程序员网站
单表:一张大表
多表:程序员表和程序员使用语言表
ID-int Name-str Gender-str From-str
Advantage-str Disadvantage-str DateTime-datetime.datetime CodeName-str
其实,如果实际开发当中,当每个程序员进入该网站(注册该网站),如果每个程序员一注册就创建一张独立的数据库表,这样的数据库就会很冗余。
假如很多程序员都在用 Python 语言写代码,也就是上面表的 CodeName 那这样就会不断的重复(重复出现 Python 。那我们为何不把重复且不变得数据,单独存一张表呢?使用时直接调用就好。
而变化得例如:程序员姓名、年龄、工龄等来创建一张表。
而且,大部分程序员会的编程语言的很多的,也就是说:一个程序员对应多门语言。
这样我们就可以建立两张表:
Users
ID-int
Name-str
Gender-str
From-str
code_ id-int
Code_
lD-int
Name-str
Advantage-str
Disadvantage-str
DateTime - datetime datetime
并不是必须按照1对多来,只是一种优化手段,为了可持续的发展
.2建表 增删
原生方法:
普及:drop database test; 删除某个文件
代码方法:
from sqlalchemy import create_engine,Column,Integer,String,ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship
建立一对多的方法,必须使用 relationship
连接数据库
engine = create_engine(
"mysql+pymysql://root:[email protected]:3306/test?charset=UTF8MB4",# (里面的 root 要填写你的密码),注意:mysql+pymysql 之间不要加空格
# "mysql + pymysql://root:root@localhost/test",
max_overflow = 5, # 超过连接池大小之后,外最多可以创建的链接
pool_size = 10, # 连接池大小
echo = True, # 调试信息展示
)
Base = declarative_base()
class User(Base): #1 1个用户会多个语言
__tablename__="user"
# 表结构
# primary_key 等于主键
# unique 唯一
# nullable 非空
# nullable = False : 不允许为空
# nullable = True : 允许为空
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125), nullable=True)
gender = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
town = Column(String(125),nullable=True)
language = relationship("Language", backref="user") #另一个表函数名 简便方式通过user可以返回,允许 language 表直接映射,访问 user 表中的数据非必须
class Language(Base): #多
__tablename__="language"
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
users_id = Column(Integer(), ForeignKey("user.id")) #关联一的哪一方 整数,外键映射关联用户 表名字.属性id
Base.metadata.create_all(engine)
添加数据:
if __name__ == '__main__':
# <---------------方法一--------------->
# 添加数据方法一
Session = sessionmaker(engine)
session = Session()
# 添加用户
user1 = User(name='张三', gender="男", town="北京")
user2 = User(name='李四', gender="女", town="天津")
session.add_all([user1, user2])
session.commit()
# 添加语言
language1 = Language(name="Python", advantage="开发快", disadvantage="运行慢")
# language2 = Language(name="C++", advantage="开发", disadvantage="测试")
language1.user = user1
session.add(language1)
session.commit()
# <---------------方法二--------------->
# 添加数据方法二(同时添加)
Session = sessionmaker(engine)
session = Session()
user3 = User(name="zevin li", gender="男",advantage="Python", disadvantage="null" )
user3.language = [
Language(name="Python", advantage="开发快", disadvantage="运行慢"),
Language(name="C", advantage="开发慢", disadvantage="运行快")
]
session.add(user3)
session.commit()
查找数据:
if __name__ == '__main__':
Session = sessionmaker(engine)
session = Session()
user_select = session.query(User).filter_by(id=3).first() #找全部是all()
print("name:>>>", user_select.name)
lan = session.query(Language).filter_by(users_id=user_select.id)
for l in lan:
print("language:>>>",l.name)
输出:
name:>>> zevin li
language:>>> Python
language:>>> C
删除数据:
if __name__ == '__main__':
Session = sessionmaker(engine)
session = Session()
use = session.query(User).filter(User.id==3).first()
session.delete(use)
session.commit()
不难发现,这样删除数据会有数据冗余,那我们该如何操作呢?
那么我们返回到最初的操作,也就是建立表的时候,加上一句
cascade='all,delete'
language = relationship("Language", backref="user", cascade='all,delete')
但在这个之前,我们首先应该删除这个已经创建的表,按照新方式重新创建表,那么我们如何删除表呢?
方法一:
Base.metadata.drop_all(engine) 直接在代码里加入即可
方法二(cmd里操作):
# 比如我们创建一个 test2的数据库
# 我们先查看已经有哪些数据库
# 不过在这之前我们需要,运行 mysql
mysql -u root -p
passowrd:****
# 上面 -u 后面需要写上你自己数据库用户名称,回车之后就输入数据库密码即可。
# 当然还可以这样登录:
mysql -u root -p123456
# 123456 直接写上你的密码
# 进入之后,查看有哪些数据库:
show databases;
# 创建数据库,不能与已有的数据库重名(大小写无所谓)
CREATE DATABASE 数据库名称;
# 创建成功就会给你返回:
Query OK, 1 row affected (0.00 sec)
# 删除表就是
drop table languages(视情况);
# 如果有关联,那就先删除它的关联表
(这里user就是和languages关联的)
# Ps: 如果要在代码中删除,那就写
drop_all()# rm - rf递归删除
删除成功之后:
执行我们更改后的代码,创建并加入数据:
from sqlalchemy import create_engine,Column,Integer,String,ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship
# 建立一对多的方法,必须使用 relationship
# 连接数据库
engine = create_engine(
"mysql+pymysql://root:[email protected]:3306/test?charset=UTF8MB4",# (里面的 root 要填写你的密码),注意:mysql+pymysql 之间不要加空格
# "mysql + pymysql://root:root@localhost/test",
max_overflow = 5, # 超过连接池大小之后,外最多可以创建的链接
pool_size = 10, # 连接池大小
echo = True, # 调试信息展示
)
Base = declarative_base()
class User(Base): #1 1个用户会多个语言
__tablename__="user"
# 表结构
# primary_key 等于主键
# unique 唯一
# nullable 非空
# nullable = False : 不允许为空
# nullable = True : 允许为空
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125), nullable=True)
gender = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
town = Column(String(125),nullable=True)
language = relationship("Language", backref="user", cascade='all,delete') #另一个表函数名 简便方式通过user可以返回,允许 language 表直接映射,访问 user 表中的数据非必须
class Language(Base): #多
__tablename__="language"
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
user_id = Column(Integer(), ForeignKey("user.id")) #关联一的哪一方 整数,外链是 user 里面的 id 表 表名字.属性id
Base.metadata.create_all(engine)
if __name__ == '__main__':
# <---------------方法一--------------->
# 添加数据方法一(语言里添加用户)
Session = sessionmaker(engine)
session = Session()
# 添加用户
user1 = User(name='张三', gender="男", town="北京")
user2 = User(name='李四', gender="女", town="天津")
session.add_all([user1, user2])
session.commit()
# 添加语言
language1 = Language(name="Python", advantage="开发快", disadvantage="运行慢")
# language2 = Language(name="C++", advantage="开发", disadvantage="测试")
language1.user = user1 #因为users_id = Column(Integer(), ForeignKey("user.id")) language = relationship("Language", backref="user")
session.add(language1)
session.commit()
<---------------方法二--------------->
# 添加数据方法二(用户里添加语言)
Session = sessionmaker(engine)
session = Session()
user3 = User(name="zevin li", gender="男",advantage="Python", disadvantage="null" )
user3.language = [
Language(name="Python", advantage="开发快", disadvantage="运行慢"),
Language(name="C", advantage="开发慢", disadvantage="运行快")
]
session.add(user3)
session.commit()
之后我们看我们的数据库:
再进行删除操作:
if __name__ == '__main__':
Session = sessionmaker(engine)
session = Session()
use = session.query(User).filter(User.id==4).first()
session.delete(use)
session.commit()
这样我们可以看到,这个注销就很完整了
更新数据:
if __name__ == '__main__':
Session = sessionmaker(engine)
session = Session()
u = session.query(User).filter(User.id==6).first()
u.name = "赵六"
session.commit()
事物回滚:
事务的回滚是指程序或数据处理错误,
将程序或数据恢复到上一次正确状态的行为。
End Transaction,失败的结束,将所有的DML(insert、update、delete)语句操作历史记录全部清空。
就例如:在我们天猫抢单时,如果你抢单失败,其实在代码中就类似于,你的代码突然运行错误,那这时候就需要恢复之前的操作,比如:选择商品、商品的属性、商品件数、促销价格等等。这些操作,其实是在操作数据库里面的数据,而此时,你没有抢单成功,就需要回滚你原本的操作。
不然,你实际是没有购买到,但数据库里面的记录是显示你有操作购买,那样数据库中的数据不完整了。所以,为了保证数据库中的数据完整性,则需要回滚操作。恢复之前的操作,也就是恢复之前对数据库的操作。
所以使用 try…except…
也就是说,在 session.commit() 之前(也就是提交数据之前),修改的数据类似于在内存缓冲区里面,执行回滚操作就是清楚缓冲区所修改的数据。
8.2多对多
一个程序员可以会多门语言_
一个语言也可以对应多个程序员.
所以一对多可以改为多对对
relationship函数是sqlalchemy对关系之间提供的一种便利的调用方式backref参数则对关系提供反向引用的声明
在最新版本的sqlalchemy中对
relationship引进了back_ populates参数,和backref作用一样,不过需要两边定义
上述代码了解即可,不必掌握(不推荐,很冗余)
建第三张表的方式:
from sqlalchemy import create_engine,Column,Integer,String,ForeignKey,Table,MetaData,select
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship
# 连接数据库
engine = create_engine(
"mysql+pymysql://root:[email protected]:3306/test?charset=UTF8MB4",# (里面的 root 要填写你的密码),注意:mysql+pymysql 之间不要加空格
# "mysql + pymysql://root:root@localhost/test",
max_overflow = 5, # 超过连接池大小之后,外最多可以创建的链接
pool_size = 10, # 连接池大小
echo = True, # 调试信息展示
)
Base = declarative_base()
User2Lan = Table("user_2_language",Base.metadata,
Column("user_id",ForeignKey("user.id"),primary_key=True),
Column("language_id",ForeignKey('language.id'),primary_key=True))
class User(Base): #1 1个用户会多个语言
__tablename__="user"
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125), nullable=True)
gender = Column(String(125),nullable=True)
town = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
language = relationship("Language", backref="user", cascade='all,delete',secondary=User2Lan) #另一个表函数名 简便方式通过user可以返回,允许 language 表直接映射,访问 user 表中的数据非必须
class Language(Base): #多
__tablename__="language"
id = Column(Integer(), primary_key=True, autoincrement=True)
name = Column(String(125),nullable=True)
advantage = Column(String(125), nullable=True)
disadvantage = Column(String(125), nullable=True)
Base.metadata.create_all(engine) #创建表
增加数据:(注意要用append方法因为是个列表):
if __name__ == '__main__':
# <---------------方法一--------------->
# 添加数据方法一(语言里添加用户)
Session = sessionmaker(engine)
session = Session()
# 添加用户
user1 = User(name='张三', gender="男", town="北京")
user2 = User(name='李四', gender="女", town="天津")
session.add_all([user1, user2])
session.commit()
# 添加语言
language1 = Language(name="Python", advantage="开发快", disadvantage="运行慢")
# language2 = Language(name="C++", advantage="开发", disadvantage="测试")
language1.user.append(user1) #操作列表
session.add(language1)
session.commit()
<---------------方法二--------------->
添加数据方法二(用户里添加语言)
Session = sessionmaker(engine)
session = Session()
user3 = User(name="zevin li", gender="男",advantage="Python", disadvantage="null" )
user3.language = [
Language(name="Python", advantage="开发快", disadvantage="运行慢"),
Language(name="C", advantage="开发慢", disadvantage="运行快")
]
session.add(user3)
session.commit()
删除和修改变化不大,但注意修改的时候是一个列表
8.3.SQLALchemy线程安全机制
每个人访问都是一个session,都是相互独立的,这样的就可以了解到,不同线程不同session,
我在线程1里操作数据库,和线程2没有任何关系,
不同线程不同session:
from sqlalchemy import create_engine,Column,Integer,String,ForeignKey,Table,MetaData,select
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,scoped_session #scopend... 为线程安全诞生
import threading
# 连接数据库
engine = create_engine(
"mysql+pymysql://root:[email protected]:3306/test?charset=UTF8MB4",# (里面的 root 要填写你的密码),注意:mysql+pymysql 之间不要加空格
# "mysql + pymysql://root:root@localhost/test",
max_overflow = 5, # 超过连接池大小之后,外最多可以创建的链接
pool_size = 10, # 连接池大小
echo = True, # 调试信息展示
)
Session = sessionmaker(bind=engine)
session = scoped_session(Session) #包裹一下 置入线程安全之中
class MyThread(threading.Thread):
def __init__(self,threadName):
super(MyThread,self).__init__()
self.name = threadName #jcheng
def run(self):
#每个线程一个seission
sess = session()
print(sess)
if __name__ == '__main__':
arr=[]
for i in range(10):
arr.append(MyThread("thread-%s" % i))
for i in arr:
i.start()
for i in arr:
i.join()
输出:
<sqlalchemy.orm.session.Session object at 0x000002450FD6DEF0>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F0F0>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F278>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F400>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F588>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F710>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F978>
<sqlalchemy.orm.session.Session object at 0x000002450FD7FBE0>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F9B0>
<sqlalchemy.orm.session.Session object at 0x000002450FD7F748>
说明这十个线程都是不同的,在线程1里操作数据库,和线程2没有任何关系,比方我在线程1里把名字改为张三,而在线程2改为李四,这两个是相互独立的,是独立的sessio,并且以最后一个提交的为主
同一线程同一session,如下代码就没开多线程
Session = sessionmaker(engine)
session = scoped_session(Session)
a = session()
b = session()
print(a)
print(b)
输出:
<sqlalchemy.orm.session.Session object at 0x0000016516A14F60>
<sqlalchemy.orm.session.Session object at 0x0000016516A14F60>
可以看到地址是一样的
不同线程不同session,同一线程同一session是利用python的TLS实现的:
同一个local对于不同的线程,不同value同一线程,同一value这就是线程隔离
import threading #线程包
a = threading.local()
a.session=1 #注册 可注册任意的 不如a.value=11111
def change(name):
try:
print(threading.current_thread().name,a.session) #线程名字。session
except:
print(threading.current_thread().name,"no session")
a.session = name
print(threading.current_thread().name,a.session)
# 不同线程注册session 名字一样 本质不一样
for i in range(3): #开启3个线程
threading.Thread(target=change,args=(i,)).start()
print(threading.current_thread().name,a.session)
输出:
Thread-1 no session
Thread-1 0
Thread-2 no session
Thread-2 1
Thread-3 no session
Thread-3 2
MainThread 1
通过输出我们就可以理解它的意思了