ORM 框架

ORM 框架

转:https://www.cnblogs.com/pycode/p/mysql-orm.html

SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。

SQLAlchemy

SQLAlchemy本身无法操作数据库,其必须依赖pymsql等第三方插件,Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:

MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
  
pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
  
MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
  
cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]

底层处理

使用 Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句。

from sqlalchemy import create_engine

#创建引擎
engine = create_engine("mysql+pymysql://lx:[email protected]:3306/Role", max_overflow=5)
#执行sql语句
engine.execute("INSERT INTO user (name) VALUES ('lx')")

result = engine.execute('select * from user')
res = result.fetchall()
print(res)

ORM功能使用
使用 ORM/Schema Type/SQL Expression Language/Engine/ConnectionPooling/Dialect 所有组件对数据进行操作。根据类创建对象,对象转换成SQL,执行SQL。

创建表

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://lx:[email protected]:3306/Role", max_overflow=5)

Base = declarative_base()

# 创建单表
class Users(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
	extra = Column(String(16))

# 一对多
class Favor(Base):
    __tablename__ = 'favor'
    nid = Column(Integer, primary_key=True)
    caption = Column(String(50), default='red', unique=True)

class Person(Base):
    __tablename__ = 'person'
    nid = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=True)
    favor_id = Column(Integer, ForeignKey("favor.nid"))

# 多对多
class ServerToGroup(Base):
    __tablename__ = 'servertogroup'
    nid = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey('server.id'))
    group_id = Column(Integer, ForeignKey('group.id'))

class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

class Server(Base):
    __tablename__ = 'server'
    id = Column(Integer, primary_key=True, autoincrement=True)
    hostname = Column(String(64), unique=True, nullable=False)
    port = Column(Integer, default=22)

Base.metadata.create_all(engine)  #创建表
# Base.metadata.drop_all(engine)   #删除表
obj = Users(name="alex0", extra='a1')
session.add(obj)
session.add_all([
    Users(name="alex1", extra='a2'),
    Users(name="alex2", extra='a3')
])
session.commit()
session.query(Users).filter(Users.id > 2).delete()
session.commit()
session.query(Users).filter(Users.id > 2).update({"name" : "099"})
session.query(Users).filter(Users.id > 2).update({Users.name: Users.name + "099"}, synchronize_session=False)
session.query(Users).filter(Users.id > 2).update({"num": Users.num + 1}, synchronize_session="evaluate")
session.commit()
ret = session.query(Users).all()
ret = session.query(Users.name, Users.extra).all()
ret = session.query(Users).filter_by(name='alex').all()
ret = session.query(Users).filter_by(name='alex').first()
  • 其他
# 条件
ret = session.query(Users).filter_by(name='alex').all()
ret = session.query(Users).filter(Users.id > 1, Users.name == 'eric').all()
ret = session.query(Users).filter(Users.id.between(1, 3), Users.name == 'eric').all()
ret = session.query(Users).filter(Users.id.in_([1,3,4])).all()
ret = session.query(Users).filter(~Users.id.in_([1,3,4])).all()
# 嵌套查询
ret = \session.query(Users).filter(Users.id.in_(session.query(Users.id).filter_by(name='eric'))).all()

# and_, or_
from sqlalchemy import and_, or_
ret = session.query(Users).filter(and_(Users.id > 3, Users.nam == 'eric')).all()
ret = session.query(Users).filter(or_(Users.id < 2, Users.name == 'eric')).all()
ret = session.query(Users).filter(
    or_(
        Users.id < 2,
        and_(Users.name=='eric', Users.id > 3),
        Users.extra != ""
    )).all()


# 通配符
ret = session.query(Users).filter(Users.name.like('e%')).all()
ret = session.query(Users).filter(~Users.name.like('e%')).all()

# 限制
ret = session.query(Users)[1:2]

# 排序
ret = session.query(Users).order_by(Users.name.desc()).all()
ret = session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all()

# 分组
from sqlalchemy.sql import func

ret = session.query(Users).group_by(Users.extra).all()
ret = session.query(
    func.max(Users.id),
    func.sum(Users.id),
    func.min(Users.id)).group_by(Users.name).all()

ret = session.query(
    func.max(Users.id),
    func.sum(Users.id),
    func.min(Users.id)).group_by(Users.name).having(func.min(Users.id) >2).all()

# 连表

ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all()

# 内连接
ret = session.query(Person).join(Favor).all()

# 外连接
ret = session.query(Person).join(Favor, isouter=True).all()


# 组合 union联合查询
q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
ret = q1.union(q2).all()

q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
ret = q1.union_all(q2).all()
  • ORM解决中文编码问题 sqlalchemy 默认使用latin-1进行编码。所以当出现中文时就会报如下错误:
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 39-41: ordinal not in range(256)

解决方法:

在连接数据库的时候直接指定字符编码:

engine = create_engine("mysql+pymysql://lx:[email protected]:3306/Role?charset=utf8", max_overflow=5,encoding='utf-8')
  • ORM 指定查询返回数据格式 默认使用query查询返回的结果为一个对象
res = session.query(User).all()
print(res)

输出结果:
[<__main__.User object at 0x10385c438>, <__main__.User object at 0x10385c4a8>, <__main__.User object at 0x10385c550>, <__main__.User object at 0x10385c5f8>, <__main__.User object at 0x10385c6a0>]

解决方法:

使用__repr__定义返回的数据

class User(Base):
    __tablename__ = 'user'
    nid = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(10),nullable=False)
    
    def __repr__(self):
        output = "(%s,%s,%s)" %(self.nid,self.name,self.role)
        return output
        
res = session.query(User).all()
print(res)

输出:
[(1,fuzj,1), (2,jie,2), (3,张三,2), (4,李四,1), (5,王五,3)]
一对多

实例

  • ORM一对多具体使用
  • 设计两张表,user表和role表
  • user 表中存放用户,role表中存放用户角色,role表中角色对应user表中多个用户,user表中一个用户只对应role表中一个角色,中间通过外键约束
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,ForeignKey
from sqlalchemy.orm import sessionmaker,relationship
from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://fuzj:[email protected]:3306/fuzj?charset=utf8", max_overflow=5,encoding='utf-8') 
Base = declarative_base()

class Role(Base):
    __tablename__ = 'role'
    rid = Column(Integer, primary_key=True, autoincrement=True)    #主键,自增
    role_name = Column(String(10))

    def __repr__(self):
        output = "(%s,%s)" %(self.rid,self.role_name)
        return output

class User(Base):
    __tablename__ = 'user'
    nid = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(10),nullable=False)
    role_id = Column(Integer,ForeignKey('role.rid'))  #外键关联

    def __repr__(self):
        output = "(%s,%s,%s)" %(self.nid,self.name,self.role)
        return output
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

#添加角色数据
session.add(Role(role_name='dba'))
session.add(Role(role_name='sa'))
session.add(Role(role_name='net'))

#添加用户数据
session.add_all([
    User(name='小红',role='1'),
    User(name='小明',role='2'),
    User(name='张三',role='2'),
    User(name='李四',role='1'),
    User(name='王五',role='3'),
])
session.commit()
session.close()
  • 普通连表查询
res = session.query(User,Role).join(Role).all()    #查询所有用户,及对应的role id
res1 = session.query(User.name,Role.role_name).join(Role).all()  #查询所有用户和角色,
res2 = \session.query(User.name,Role.role_name).join(Role,isouter=True).filter(Role.role_name=='sa').all() #查询所有sa的用户
print(res)
print(res1)
print(res2)

输出结果:
[((1,小红,1), (1,dba)), ((2,小明,2), (2,sa)), ((3,张三,2), (2,sa)), ((4,李四,1), (1,dba)), ((5,王五,3), (3,net))]
[('小红', 'dba'), ('小明', 'sa'), ('张三', 'sa'), ('李四', 'dba'), ('王五', 'net')]
[('jie', 'sa'), ('张三', 'sa')]
使用relationship 添加影射关系进行查询(反向查询)
  • 首先在User表中添加relationship映射关系
class Role(Base):
    __tablename__ = 'role'
    rid = Column(Integer, primary_key=True, autoincrement=True)    #主键,自增
    role_name = Column(String(10))

    def __repr__(self):
        output = "(%s,%s)" %(self.rid,self.role_name)
        return output

class User(Base):
    __tablename__ = 'user'
    nid = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(10),nullable=False)
    role_id = Column(Integer,ForeignKey('role.rid'))
    role = relationship("Role",backref='user')    #Role为类名
    
    def __repr__(self):
        output = "(%s,%s,%s)" %(self.nid,self.name,self.role)
        return output
  • 查询
#正向查询
print('正向查询')
res = session.query(User).all()  #查询所有的用户和角色
for u in res:
    print(u.name,u.role.role_name)    #此时的u.role 就是role表对应的关系
res = session.query(User).filter(User.name=='小明').first()  #查询fuzj用户和角色
print(res.name,res.role.role_name)

print('反向查找')
#反向查找
res = session.query(Role).filter(Role.role_name =='dba').first()   #查找dba组下的所有用户
print(res.user)   #此时 print的结果为[(1,小明,1), (4,李四,1)]
for i in res.user:
    print(i.name,res.role_name)

输出结果:
正向查询
小明 dba
小红 sa
张三 sa
李四 dba
王五 net
小明 dba
反向查找
[(1,小明,1), (4,李四,1)]
小明 dba
李四 dba
  • 说明

relationship 在user表中创建了新的字段,这个字段只用来存放user表中和role表中的对应关系,在数据库中并不实际存在
正向查找: 先从user表中查到符合name的用户之后,此时结果中已经存在和role表中的对应关系,group对象即role表,所以直接使用obj.group.role_name就可以取出对应的角色
反向查找:relationship参数中backref=‘uuu’,会在role表中的每个字段中加入uuu,而uuu对应的就是本字段在user表中对应的所有用户,所以,obj.uuu.name会取出来用户名
所谓正向和反向查找是对于relationship关系映射所在的表而说,如果通过该表(user表)去查找对应的关系表(role表),就是正向查找,反正通过对应的关系表(role表)去查找该表(user表)即为反向查找。而relationship往往会和ForeignKey共存在一个表中。

ORM多对多

实例

Mysql多对多关系指的是两张表A和B本没有任何关系,而是通过第三张表C建立关系,通过关系表C,使得表A在表B中存在多个关联数据,表B在表A中同样存在多个关联数据

  • 创建三张表 host表 hostuser表 host_to_hostuser表
  • host表中存放主机,hostuser表中存放主机的用户, host_to_hostuser表中存放主机用户对应的主机,hostuser表中用户对应host表中多个主机,host表中主机对应hostuser表中多个用户,中间关系通过host_to_hostuser表进行关联。host_to_hostuser和host表、user表进行外键约束
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,ForeignKey
from sqlalchemy.orm import sessionmaker,relationship
from sqlalchemy import create_engine
class Host(Base):
    __tablename__ = 'host'
    nid = Column(Integer, primary_key=True,autoincrement=True)
    hostname = Column(String(32))
    port = Column(String(32))
    ip = Column(String(32))

class HostUser(Base):
    __tablename__ = 'host_user'
    nid = Column(Integer, primary_key=True,autoincrement=True)
    username = Column(String(32))

class HostToHostUser(Base):
    __tablename__ = 'host_to_host_user'
    nid = Column(Integer, primary_key=True,autoincrement=True)
    host_id = Column(Integer,ForeignKey('host.nid'))
    host_user_id = Column(Integer,ForeignKey('host_user.nid'))

Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

#添加数据
session.add_all([
    Host(hostname='c1',port='22',ip='1.1.1.1'),
    Host(hostname='c2',port='22',ip='1.1.1.2'),
    Host(hostname='c3',port='22',ip='1.1.1.3'),
    Host(hostname='c4',port='22',ip='1.1.1.4'),
    Host(hostname='c5',port='22',ip='1.1.1.5'),
])

session.add_all([
    HostUser(username='root'),
    HostUser(username='db'),
    HostUser(username='nb'),
    HostUser(username='sb'),
])

session.add_all([
    HostToHostUser(host_id=1,host_user_id=1),
    HostToHostUser(host_id=1,host_user_id=2),
    HostToHostUser(host_id=1,host_user_id=3),
    HostToHostUser(host_id=2,host_user_id=2),
    HostToHostUser(host_id=2,host_user_id=4),
    HostToHostUser(host_id=2,host_user_id=3),
])

session.commit()
session.close()
  • 普通多次查询
第一步:host_id = session.query(Host.nid).filter(Host.hostname=='c2').first()   #查找hostbane对应的hostid,返回结果为元组(2,)

第二步: user_id_list = session.query(HostToHostUser.host_user_id).filter(HostToHostUser.host_id==host_id[0]).all()  #查询hostid对应的所有userid

user_id_list = zip(*user_id_list)   #user_id_list 初始值为[(2,), (4,), (3,)],使用zip转换为[2,4,3]对象
#print(list(user_id_list))    #结果为[(2, 4, 3)]

第三步:user_list = session.query(HostUser.username).filter(HostUser.nid.in_(list(user_id_list)[0])).all()  #查询符合条件的用户
print(user_list)

#或者:
user_id_list = session.query(HostToHostUser.host_user_id).join(Host).filter(Host.hostname=='c2').all()
user_id_list = zip(*user_id_list)
user_list = session.query(HostUser.username).filter(HostUser.nid.in_(list(user_id_list)[0])).all()
print(user_list)

# 输出结果
[('db',), ('nb',), ('sb',)]
使用relationship映射关系查询
  • 首先在关系表Host_to_hostuser中加入relationship关系映射
class HostToHostUser(Base):
    __tablename__ = 'host_to_host_user'
    nid = Column(Integer, primary_key=True,autoincrement=True)

    host_id = Column(Integer,ForeignKey('host.nid'))
    host_user_id = Column(Integer,ForeignKey('host_user.nid'))
    host = relationship('Host',backref='h') #对应host表
    host_user = relationship('HostUser',backref='u') #对应host_user表
  • 查询
#查找一个服务器上有哪些用户
res = session.query(Host).filter(Host.hostname=='c2').first()  #返回的是符合条件的服务器对象
res2 = res.h    #通过relationship反向查找 Host_to_Hostuser中的对应关系
for i in res2:   #i为host_to_hostuser表和host表中c2主机有对应关系的条目
    print(i.host_user.username)        #正向查找, 通过relationship ,找到host_to_hostuser中对应的hostuser 即i.host_user

#查找此用户有哪些服务器
res = session.query(HostUser).filter(HostUser.username=='sb').first()
for i in res.u:
    print(i.host.hostname)
扩展查询
  • 不查询关系表(中间表),直接在hostuser表中指定关系表,然后获取host表
  • 在host表中使用 relationship的secondary指定关系表。
class Host(Base):
    __tablename__ = 'host'
    nid = Column(Integer, primary_key=True,autoincrement=True)
    hostname = Column(String(32))
    port = Column(String(32))
    ip = Column(String(32))
    host_user = relationship('HostUser',secondary=lambda :HostToHostUser.__table__,backref='h')

注意使用lambda是为了使表的顺序不在闲置

查询:

host_obj = session.query(Host).filter(Host.hostname=='c1').first()
for i in host_obj.host_user:
    print(i.username)

创建一个实时监控文件系统

我将使用 SQLAlchemy 演示如何创建一个元数据工具。此工具的目标是监控文件系统、创建和删除事件,以及在一个 SQLAlchemy 数据库中保存这些变更的记录

#/usr/bin/env python2.7

import os
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
 
from pyinotify import *
 
path = "/tmp"
 
#SQLAlchemy
engine = create_engine('sqlite:///meta.db', echo=True)
Base = declarative_base()
Session = scoped_session(sessionmaker(bind=engine))
 
class Filesystem(Base):
    __tablename__ = 'filesystem'
    path = Column(String, primary_key=True)
    name = Column(String)
 
    def __init__(self, path,name):
        self.path = path
        self.name = name
 
    def __repr__(self):
        return "<Metadata('%s','%s')>" % (self.path,self.name)
 
# 装饰器
def transactional(fn):
    """add transactional semantics to a method."""
 
    def transact(self, *args):
        session = Session()
        try:
            fn(self, session, *args)
            session.commit()
        except:
            session.rollback()
            raise
    transact.__name__ = fn.__name__
    return transact
 
 
class ProcessDir(ProcessEvent):
    """Performs Actions based on mask values"""
 
    @transactional
    def process_IN_CREATE(self, session, event):
        print "Creating File and File Record:", event.pathname
        create_record = Filesystem(event.pathname, event.path)
        session.add(create_record)
 
    @transactional
    def process_IN_DELETE(self, session, event):
        print "Removing:", event.pathname
        delete_record = session.query(Filesystem).\
            filter_by(path=event.pathname).one()
        session.delete(delete_record)
 
def init_repository():
    #Drop the table, then create again with each run
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)
    session = Session()
 
    #Initial Directory Walking Addition Brute Force
    for dirpath, dirnames, filenames in os.walk(path):
        for file in filenames:
            fullpath = os.path.join(dirpath, file)
            record = Filesystem(fullpath, file)
            session.add(record)
        session.flush()
 
    for record in session.query(Filesystem):
        print "Database Record Number: Path: %s , File: %s " \
        % (record.path, record.name)
 
    session.commit()
 
if __name__ ==  "__main__":
 
    init_repository()
 
    #Pyionotify
    wm = WatchManager()
    mask = IN_DELETE | IN_CREATE
    notifier = ThreadedNotifier(wm, ProcessDir())
    notifier.start()
 
    wdd = wm.add_watch(path, mask, rec=True)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章