sqlalchemy使用

目的

总结项目中使用到sqlalchemy的部分。(python中的对象映射技术,更方便地使用)

使用

基础

安装

pip install SQLAlchemy -i https://pypi.douban.com/simple
pip install mysql-connector-python -i https://pypi.douban.com/simple

 

增删改查

# 主要引用自廖雪峰老师的使用SQLAlchemy
# 导入:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# 创建对象的基类:
Base = declarative_base()

# 定义User对象:
class User(Base):
    # 表的名字:
    __tablename__ = 'user'

    # 表的结构:
    id = Column(Integer, primary_key=True) 
    name = Column(String(20))
# 动态添加属性
setattr(User, ‘'age', (Column('age', Integer, nullable=True)))
# 初始化数据库连接:
 engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test') 
# 创建DBSession类型: 
DBSession = sessionmaker(bind=engine) 

# 创建session对象: 
session = DBSession() 

# 创建新User对象: #数据对象得到创建,此时为Transient状态 
new_user = User(id=5, name='Bob') 

# 添加到session: #数据对象被关联到session上,此时为Pending状态 
session.add(new_user) 

# 提交即保存到数据库: #数据对象被推到数据库中,此时为Persistent状态 
session.commit() 

# 关闭session:  
session.close() 

# 查询 
user = session.query(User).filter(User.id==5).first(); 
# user = session.query(User).get(5); # get参数为id  
# user = session.query(User).filter_by(id=5).first(); #条件 

# 删除,在查询基础上 
session.delete(user) 

# 改,在查询基础上 
user.name='Alice' 
session.commit() 
session.close()

数据库操作

def init_db():
    """初始化生成上述所有表,可重复执行,内部自动判断表是否存在"""
    Base.metadata.create_all(engine)

def drop_db():
    # 删除表
    Base.metadata.drop_all(engine)

一些区分项

  1. filter与filter_by: 前者条件更为丰富,插入的是条件Model.item == Number; filter_by插入的是kwargs键值对,item=Number
  2. 查询的启动项,即session.query(xxxx).xxx()的最后一项:主要有返回单个值得first, scalar及多个值的all, 其中 first返回第一个结果或者空,scalar如果有多个结果时报错,其他情况同first。
  3. filter或者filter_by中,多个条件或语句逗号隔开,默认为“and”关系, or需要引入filter_by(or_(rule1, rule2))

高级

对session进行包装

 

@contextmanager
def scope_session(DBSession):
    session = DBSession()
    try:
        yield session    # 返回session对象
        session.commit()
    except Exception as e:
        session.rollback()  # 异常时回滚,否则之后所有orm操作全部失效
        logger.error(f"{e}", exc_info=True)  # exc_info=True可输出错误栈
    finally:
        session.close()
        DBSession.remove()  # 释放session,用于多线程时,防止数据库连接超时失效

# 使用方式
with scope_session(DBSession) as session:
    user = session.query(User).get(5)

session使用进程池及防止长时间失效

engine = create_engine(DB_URL,  # DB_URL同上
                            max_overflow=5,  # 超过连接池大小外最多创建的连接
                            pool_size=10,  # 连接池大小
                            pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
                            pool_recycle=7200,  # 多久之后对线程池中的线程进行一次连接的回收(重置)
                            echo=False,  # 是否回显
                            isolation_level="READ_COMMITTED" # 实时监控数据库变化
                            )

# engine修改为上面代码,使用后DBSession.remove()归还连接到线程池。

关联查询relationship

from sqlalchemy.orm import relationship, foreign, remote
from sqlalchemy import Column, Integer, String
class Book(Base):
    __tablename__ = 'book'
id = Column(Integer, primary_key=True)
isbn = Column(String(11), nullable=False, comment="isbn编码")
    owner_id = Column(Integer, comment="拥有者")

    user= relationship(
        'User',   # 引用的对象
        uselist=True,  # 结果是单个还是列表
        primaryjoin=foreign(owner_id) == remote(User.id), 
        backref="books")  

# foreign与remote可以实现在数据库不设置外键的情况下代码中以外键使用。
user.books  or book.user

批量操作

User类下添加属性方法
    @property
    def info_dict(self):
        _needs = ['id', 'name']
        return {name: getattr(self, name) for name in _needs}

users = [User(id=1, name='Yin'), User(id=2, name='Yang']
with scope_session(DBSession) as session:
    # 法一
    session.bulk_save_objects(users)  # 添加对象列表,增加参数 
    session.bulk_save_objects(users, return_defaults=True)#可返回修改部分,例如主键为自增,当前项id=None, 运行后users中的对象id为数据库id
    # 法二
    user_dicts = [user.info_dict for user in users]
    session.bulk_insert_mappings(User, user_dicts)  # 插入
    session.bulk_update_mappings(User, user_dicts) # 更新

其他

# 从一个数据库中查询一个对象插入到另一个数据库中, 修改对象状态。
from sqlalchemy.orm import make_transient
with scope_session(DBSession1) as session:
    user = session.query(User).get(5)
    make_transient(user)
    with scope_session(DBSession2) as target_sess:
        target_sess.add(user)

# 执行mysql语句
session.execute('call mysql_procedure();')
# 对象丢失时使用或跨session使用对象
session.merge(user) 

#复杂查询
user_ids = {1, 2, 3}
results = session.query(User.id, Book).filter(User.id.in_(user_ids)).join(Book, User.id == Book.owner_id).order_by(User.id).all()


# 多线程使用
from concurrent.futures import ThreadPoolExecutor, as_completed
# 先用一个session查询相关对象,将查出的对象传入函数中,在函数中新建session,再merge; 或者把session也传入。
executor = ThreadPoolExecutor(max_workers=4)
def test(user):
  print(f"hello {user}")
   with scope_session(DBSession) as session:
    books = session.query(Book).filter(Book.owner_id== user.id).all()
       for book in books:
      print(f"{user.name} own book {book.isbn}")
   return True
def get_result(future):
  data = future.result()
   print(data)
user = session.query(User).get(5)   
executor.submit(test, user).add_done_callback(get_result)

#判断数据库中NULL的字段
user = session.query(User).filter(User.name.is_(None)).first();