Python后端开发flask—数据库与缓存

sqlalchemy

导入模块

from flask_sqlalchemy import SQLAlchemy

创建和配置app

# 创建
app = Flask(__name__, static_folder='../static')
ctx = app.app_context()	# 解决RuntimeError: Working outside of application context.
ctx.push()

# 配置
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/egl_flask'
# 一般用下面这个带pymysql的,因为上面的在高版本flask会报错
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:[email protected]:3306/mydb'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_MAX_OVERFLOW'] = 5
app.config['SQLALCHEMY_POOL_SIZE'] = 20
app.config['SQLALCHEMY_POOL_TIMEOUT'] = 5
app.secret_key = 'Terraria'

db对象

app = Flask(__name__, static_folder='./static')

# 创建db对象
db = SQLAlchemy(app)

# 数据模型...

# 测试
if __name__ == '__main__':
    # 重置数据库
    db.drop_all()
    # 新建
    db.create_all()
    # 生成数据
    user1 = User(username='90217', password='123abc', status=1)
    user2 = User(username='Azir', password='123abc', status=0)
    user3 = User(username='demanwei', password='123abc')
    user4 = User(username='90219', password='123abc')
    # 把数据提交给用户会话
    db.session.add_all([user1, user2, user3, user4])
    # 提交事务
    db.session.commit()

数据模型

模型类

class User(db.Model):
	# 表名
	__tablename__ = 'user'
	
	# 字段
	id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
	username = db.Column(db.String(256), unique=True ,nullable=False)
	password = db.Column(db.String(256), nullable=False)
    status = db.Column(db.Integer, nullable=False, default=1)
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
	
    # 描述信息,类似于JavaBean的toString()
	def __repr__(self):
        desc = self.__class__.__name__ + '{'
        for field in self.keys():
            desc += '{}={},'.format(field, getattr(self, field))
        desc += '}'
        return desc

Column数据类型

参数 说明 DB对应类型
db.BigInteger 长整型,一般做主键 bitint
db.Integer 整形 int
db.String(n) 字符型,使用时需要指定长度,区别于Text类型 varchar(n)
db.Text 文本型 text
db.LONGTEXT 长文本型 longtext
db.Float 浮点型 float
db.Double 双精度浮点型 double
db.Boolean 布尔型 tinyint
db.Decimal(a,b) 具有小数点而且数值确定的数值,一般用于存储金钱 decimal
db.Enum 枚举类型
db.DateTime 日期时间类型 datetime
db.Date 日期类型,传递datetime.date() date
db.Time 时间类型,传递datetime.time() time

字段的参数设置

参数 说明
primary_key 是否为主键
autoincrement 是否自动增长
unique 是否唯一
default 默认值
nullable 是否允许为空
index 是否创建索引
onupdate 更新的时候执行的函数
name 该属性在数据库中的字段映射

动态修改实体属性

对于实体属性的修改,只能采用entity.field = value的方式:

user = User.query.get('1')
# 正确方式
user.status = 0
# 错误方式
user['status'] = 0
user.'status' = 0

假如现在有一个接口是修改用户信息,前端传来的字段至少有user_id,但其他字段是不确定的。此时一般想到会这样做:

# 动态修改user字段, 如user.status=0,user.password=123456
for field, value in request.json.items():
    eval('user.{} = {}'.format(field, value))

但不知什么原因,会报如下错误。并且,在Python中并不建议使用eval()运行代码,因为这可能会有未知的风险。
Python后端开发flask—数据库与缓存_第1张图片

正确的做法是使用内置的setattr(obj, name, value),它用来修改对象的属性,注意name参数是str类型表示属性的名称

setattr(user, 'status', 0)

因此,该接口的全部代码如下:

@bp_user.route('/modify', methods=['POST'])
def modify():
    user_id = request.json.pop('id')
    if not user_id:
        return jsonify(status=0, message='字段缺失', data=None)
    # 传来什么字段就修改哪个
    user = User.query.get(user_id)
    if not user:
        return jsonify(status=-1, message='未找到该用户', data=None)
    for field, value in request.json.items():
        setattr(user, field, value)
    db.session.commit()
    return jsonify(status=1, message='操作成功', data=None)

外键与级联

CASCADE策略

假设,user和user_info表存在外键关系,user_info的user_id字段外键对应use表的id字段,想实现级联删除/更新:

  • CASCADE策略:如果外键对应表的主键行删除/更新,那么关联表的行也删除/更新
  • 注意cascade='all,delete'就已经代表了级联删除和级联更新,没有update这一项
class User(db.Model):
	__tablename__ = 'user'
	
	id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
	username = db.Column(db.String(256), unique=True ,nullable=False)
	password = db.Column(db.String(256), nullable=False)
    status = db.Column(db.Integer, nullable=False, default=1)
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
	
	# 关系
	user_userinfo = db.relationship('UserInfo', backref='user.id', cascade='all,delete')
	
	def __repr__(self):
        desc = self.__class__.__name__ + '{'
        for field in self.keys():
            desc += '{}={},'.format(field, getattr(self, field))
        desc += '}'
        return desc
	

class UserInfo(db.Model):
	__tablename__ = 'userinfo'
    
	id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
	user_id = db.Column(db.BigInteger, db.ForeignKey('user.id', ondelete='CASCADE', onupdate='CASCADE'))
	sex = db.Column(db.String(8), nullable=False, default='未知')
	age = db.Column(db.Integer, nullable=True)
	region = db.Column(db.String(64), nullable=True)
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
	
	def __repr__(self):
        desc = self.__class__.__name__ + '{'
        for field in self.keys():
            desc += '{}={},'.format(field, getattr(self, field))
        desc += '}'
        return desc

SET NULL策略

  • SET NULL策略:如果外键对应表的主键行删除/更新,那么关联表的行对应的外键字段设置为NULL
  • 实现删除为SET NULL策略,而更新为CASCADE策略:
class User(db.Model):
	__tablename__ = 'user'
	
	id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
	username = db.Column(db.String(256), unique=True ,nullable=False)
	password = db.Column(db.String(256), nullable=False)
    status = db.Column(db.Integer, nullable=False, default=1)
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
	
	# 关系
	user_userinfo = db.relationship('UserInfo', backref='user.id', cascade='all,delete,delete-orphan')
	
	def __repr__(self):
        desc = self.__class__.__name__ + '{'
        for field in self.keys():
            desc += '{}={},'.format(field, getattr(self, field))
        desc += '}'
        return desc
	

class UserInfo(db.Model):
	__tablename__ = 'userinfo'
    
	id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
	user_id = db.Column(db.BigInteger, db.ForeignKey('user.id', ondelete='SET NULL', onupdate='CASCADE'))
	sex = db.Column(db.String(8), nullable=False, default='未知')
	age = db.Column(db.Integer, nullable=True)
	region = db.Column(db.String(64), nullable=True)
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now)
	
	def __repr__(self):
        desc = self.__class__.__name__ + '{'
        for field in self.keys():
            desc += '{}={},'.format(field, getattr(self, field))
        desc += '}'
        return desc

CRUD

参考

@app.route('/user/add')
def user_add():
    """ 插入 """
    # 单个插入
    new_user = User(
        account='111111',
        username='flask_test',
        password='111111',
    )
    db.session.add(new_user)
    # 回滚事务
    # db.session.rollback()
    # 提交事务
    db.session.commit()

    ## 批量插入
    # db.session.add_all([user0,user1,user2...])
    # db.session.commit()
    return '添加成功'

单个删除:

@app.route('/user/delete')
def user_delete():
    """ 删除用户 """
    user = User.query.get('111111')
    if user:
        db.session.delete(user)
        db.session.commit()
        return '删除成功'
    return '该用户不存在'

批量删除:

User.query.filter(User.status == 0).delete(synchronize_session=False)
db.session.commit()

全部删除:

User.query.delete(synchronize_session=False)
db.session.commit()

@app.route('/user/update')
def user_update():
    """ 更新 """
    user = User.query.get('90217')
    if user:
        user.score = 666
        db.session.commit()
        return '修改成功'
    return '该用户不存在'

  • 查询所有
    @app.route('/user/list')
    def user_list():
        """ 查询所有 """
        # 返回list
        user_list = User.query.all()
        return str(user_list)
    
  • 根据主键查询单个:
    @app.route('/user/get/')
    def user_get(account):
        """ 根据主键查询 """
        # 查询不到返回None
        # user = User.query.get(account)
        # 查询不到返回404NotFound
        user = User.query.get_or_404(account)
        return str(user)
    
  • 条件查询:
    @app.route('/user/filter')
    def user_filter():
        """ 过滤查询 """
        ## 获取多个
        # filter_by:多字段,之间and关系
        # users = User.query.filter_by(isAdmin=1).all()
        # filter: 参数是 SQL Expression
        # 等于 ==
        users = User.query.filter(User.isAdmin == 0).all()
        # 不等于 !=
        # users = User.query.filter(User.isAdmin != 0).all()
        # 模糊查询
        # users = User.query.filter(User.username.like('%ang')).all()
        # 在范围 in_
        # users = User.query.filter(User.username.in_(['Shuang','Azir'])).all()
        # 不在范围 ~ in_
        # users = User.query.filter(~User.username.in_(['Shuang', 'Azir'])).all()
        # 为Null == None
        # users = User.query.filter(User.avatarUrl==None).all()
        # 不为Null != None
        # users = User.query.filter(User.avatarUrl != None).all()
        # 或 or_
        # users = User.query.filter(or_(
        # 	User.score == 0,
        # 	User.level == 0
        # )).all()
    
        ## 获取第1个
        user = User.query.filter(User.isAdmin == 0).first()
    
        resp = '''
    	

    [.all()] users: {}

    [.first()] user: {}

    '''
    .format(users, user) return resp

高级

排序

@bp_user.route('/list', methods=['GET'])
def list_user():
    # 按照create_time降序排序
    user_list = User.query.order_by(User.create_time.desc()).all()
    return jsonify(status=1, message='获取成功', data=data={
        'title': User.keys(),
        'user_list': user_list
    })

分页查询

@bp_user.route('/list', methods=['GET'])
def list_user():
    # 必须是int类型,因为paginate()要求就是传int
    current = request.args.get('current', type=int)
    size = request.args.get('size', type=int)
    # 分页, error_out=False表示没查到返回空列表`[]`
    pagination = User.query.order_by(User.create_time.desc()).paginate(page=current, per_page=size, error_out=False)
    return jsonify(status=1, message='获取成功', data={
        'title': User.keys(),
        'user_list': pagination.items,# pagination.items: 返回当前页的内容列表
        'current': current,
        'size': size,
        'pages': pagination.pages,
        'has_next': pagination.has_next,
        'has_prev': pagination.has_prev,
        'total': pagination.total,
    })

count统计

from sqlalchemy import func

@bp_user.route('/count', methods=['GET'])
def count_user():
    """ 获取用户数量 """
    user_count = User.query(func.count())
    return jsonify(status=1, message='获取成功', data=user_count)

distinct去重

@bp_user.route('/allPassword', methods=['GET'])
def all_password():
    """ 获取所有用户都使用了哪些密码 """
    user_list = User.query.with_entities(User.password.distinct()).all()
    distinct_passwords = [row[0] for row in user_list]
    return jsonify(status=1, message='获取成功', data=distinct_passwords)

分组

from src.db_model import Correct
from itertools import groupby

# 查询整个列表
correct_list = Correct.query.order_by(Correct.create_time.desc()).all()
# 以uuid4作为分组字段
grouped_data = groupby(correct_list, lambda c: c.uuid4)
# 遍历数据, k:分组字段的实际值, v:分组内的实体列表
for k, v in grouped_data:
    list_for_key = [d for d in v]
    print(k, '=>', list_for_key)

Python后端开发flask—数据库与缓存_第2张图片

缓存技术

memcached

import memcache

# 在连接之前,一定要先启动memcached
client = memcache.Client({'127.0.0.1:11211'}, debug=True)

username = client.set('username', 'lzc', time=60)
client.set_multi({'title': '美君!', 'content': '你好,在吗'}, time=60)

print(username)

redis

创建Redis对象

from redis import Redis

HOST = '127.0.0.1'
IP = 6379
PASSWORD = 'your_password'

pedis = Redis(host=HOST, port=IP, db=1, password=PASSWORD, decode_responses=True)

keys命令

def demo_keys():
	keys = pedis.keys("socketex:message:*")
	for key in keys:
		data = pedis.hgetall(key)
		print(data)

操作string

key = 'username'
# 获取
print(pedis.get(key))
# 设置
pedis.set(key, '魏德曼')
print(pedis.get(key))
# 删除
pedis.delete(key)
print(pedis.get(key))
操作list
key = 'language'
# lpush
pedis.lpush(key, 'Java','Python')
# rpush
pedis.rpush(key, 'C++','C#')
pedis.lpush(key, 'JavaScript')
# 获取范围内的元素: JavaScript Python Java  C++ C#
print(pedis.lrange(key, 0, -1))
# 删除
pedis.delete(key)

操作set

key = 'team'
# 添加元素
pedis.sadd(key, 0)
pedis.sadd(key, '1')
pedis.sadd(key, 1)
pedis.sadd(key, '2')
pedis.sadd(key, 1)
# 获取元素
print(pedis.smembers(key))
# 获取元素个数
print(pedis.scard(key))
# 判断元素是否在set种
print(pedis.sismember(key,'2'))
# 删除
pedis.delete(key)

操作hash

key = 'web'
# 设置一个
pedis.hset(key, 'baidu', 'www.baidu.com')
pedis.hset(key, 'jd', 'www.jd.com')
# 获取一个
print(pedis.hget(key,'baidu'))
# 获取所有
print(pedis.hgetall(key))
print('='*40)

# 获取keys
print(pedis.hkeys(key))
# 获取values
print(pedis.hvals(key))
print('=' * 40)

# field不存在添加,否则不执行
pedis.hsetnx(key,'ncut','www.ncut.edu.cn')
pedis.hsetnx(key,'jd','www.360buy.com')
print(pedis.hgetall(key))

# 删除
pedis.delete(key)

操作事务(管道)

pipe = pedis.pipeline()
pipe.set('account', '90217')
pipe.set('password', '123abc')
pipe.execute()

发布与订阅

## 发布与订阅功能->异步发送邮件
key = 'email'
ps = pedis.pubsub()  # 获取对象
ps.subscribe(key)  # 订阅频道
# 监听
def listen():
	while True:
		for item in ps.listen():  # 持续监听,这是一个生成器
			if item['type'] == 'message':
				data = item['data']
				print(data)
t_listen = threading.Thread(target=listen)
t_listen.start()
	
# 发布消息
def publish():
	while True:
		pedis.publish(key, '[[email protected]]发来的邮件: '+str(time.time()))
		time.sleep(random.random()*4)	# 随机等待一段时间

t_publish = threading.Thread(target=publish)
t_publish.start()

你可能感兴趣的:(Python,#,后端开发flask,python,flask,数据库)