【python爬虫 系列】8.SQLALchemy与MYSQL数据库进阶

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

通过输出我们就可以理解它的意思了

发布了57 篇原创文章 · 获赞 59 · 访问量 9695

猜你喜欢

转载自blog.csdn.net/AI_LINNGLONG/article/details/104545407