從ORM框架到SQLAlchemy

1、ORM

1.什麼是ORM

對象-關係映射(Object-Relational Mapping,簡稱ORM),面向對象的開發方法是當今企業級應用開發環境中的主流開發方法,關係數據庫是企業級應用環境中永久存放數據的主流數據存儲系統。對象和關係數據是業務實體的兩種表現形式,業務實體在內存中表現爲對象,在數據庫中表現爲關係數據。內存中的對象之間存在關聯和繼承關係,而在數據庫中,關係數據沒法直接表達多對多關聯和繼承關係。所以,對象-關係映射(ORM)系統通常以中間件的形式存在,主要實現程序對象到關係數據庫數據的映射。java

 

 

2.爲何使用ORM?

當咱們實現一個應用程序時(不使用O/R Mapping),咱們可能會寫特別多數據訪問層的代碼,從數據庫保存、刪除、讀取對象信息,而這些代碼都是重複的。而使用ORM則會大大減小重複性代碼。對象關係映射(Object Relational Mapping,簡稱ORM),主要實現程序對象到關係數據庫數據的映射。python

3.對象-關係映射解釋:

3.1 ORM方法論基於四個核心原則:

  •  簡單:ORM以最基本的形式建模數據。好比ORM會將MySQL的一張表映射成一個Java類(模型),表的字段就是這個類的成員變量
  •  精確:ORM使全部的MySQL數據表都按照統一的標準精確地映射成java類,使系統在代碼層面保持準確統一
  •  易懂:ORM使數據庫結構文檔化。好比MySQL數據庫就被ORM轉換爲了java程序員能夠讀懂的java類,java程序員能夠只把注意力放在他擅長的java層面(固然可以熟練掌握MySQL更好)
  •  易用:ORM包含對持久類對象進行CRUD操做的API,例如create(), update(), save(), load(), find(), find_all(), where()等,也就是講sql查詢所有封裝成了編程語言中的函數,經過函數的鏈式組合生成最終的SQL語句。經過這種封裝避免了不規               範、冗餘、風格不統一的SQL語句,能夠避免不少人爲Bug,方便編碼風格的統一和後期維護。

面向對象是從軟件工程基本原則(如耦合、聚合、封裝)的基礎上發展起來的,而關係數據庫則是從數學理論發展而來的,兩套理論存在顯著的區別。爲了解決這個不匹配的現象,對象關係映射技術應運而生。O/R中字母O起源於"對象"(Object),而R則來自於"關係"(Relational)。幾乎全部的程序裏面,都存在對象和關係數據庫。在業務邏輯層和用戶界面層中,咱們是面向對象的。當對象信息發生變化的時候,咱們須要把對象的信息保存在關係數據庫中。mysql

3.2 通常的ORM包括如下四部分:  

  •    一個對持久類對象進行CRUD操做的API;  
  •    一個語言或API用來規定與類和類屬性相關的查詢;  
  •    一個規定mapping metadata的工具;  
  •     一種技術可讓ORM的實現同事務對象一塊兒進行dirty checking, lazy association fetching以及其餘的優化操做。

4. ORM的優缺點:

優勢:程序員

  1. 提升開發效率,下降開發成本
  2. 使開發更加對象化
  3. 可移植
  4. 能夠很方便地引入數據緩存之類的附加功能

缺點:sql

  1. 自動化進行關係數據庫的映射須要消耗系統性能。其實這裏的性能消耗還好啦,通常來講均可以忽略之。
  2. 在處理多表聯查、where條件複雜之類的查詢時,ORM的語法會變得複雜。

5.經常使用框架

  1. Hibernate 全自動 須要寫hql語句
  2. iBATIS 半自動 本身寫sql語句,可操做性強,小巧
  3. mybatis
  4. eclipseLink
  5. JFinal
  6. -----許多

 

2、ORM框架:SQLAlchemy

sqlalchemy是python編程語言下的一款ORM框架,該框架創建在數據庫API上,使用關係對象映射進行數據庫操做,簡言之即是:將對象轉換成SQL,而後使用數據API執行SQL並獲取執行結果。數據庫

 

 

須要本身把數據庫中的表映射成類,而後才能經過對象的方式去調用。SQLAlchemy不止能夠支持MYSQL,還能夠支持Oracle等。express

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...]

案例:緩存

1.安裝SQLAlchemy:

pip install  SQLAlchemy
import sqlalchemy
print([obj for obj in dir(sqlalchemy) if not obj.startswith("__")])

['ARRAY', 'BIGINT', 'BINARY', 'BLANK_SCHEMA', 'BLOB', 'BOOLEAN', 'BigInteger', 'Binary', 'Boolean', 'CHAR', 'CLOB', 'CheckConstraint', 'Column', 'ColumnDefault', 'Constraint', 'DATE', 
'DATETIME', 'DDL', 'DECIMAL', 'Date', 'DateTime', 'DefaultClause', 'Enum', 'FLOAT', 'FetchedValue', 'Float', 'ForeignKey', 'ForeignKeyConstraint', 'INT', 'INTEGER', 'Index', 
'Integer', 'Interval', 'JSON', 'LargeBinary', 'MetaData', 'NCHAR', 'NUMERIC', 'NVARCHAR', 'Numeric', 'PassiveDefault', 'PickleType', 'PrimaryKeyConstraint', 'REAL', 'SMALLINT', 
'Sequence', 'SmallInteger', 'String', 'TEXT', 'TIME', 'TIMESTAMP', 'Table', 'Text', 'ThreadLocalMetaData', 'Time', 'TypeDecorator', 'Unicode', 'UnicodeText', 'UniqueConstraint', 
'VARBINARY', 'VARCHAR', 'alias', 'all_', 'and_', 'any_', 'asc', 'between', 'bindparam', 'case', 'cast', 'collate', 'column', 'cprocessors', 'create_engine', 'cresultproxy', 'cutils', 
'delete', 'desc', 'dialects', 'distinct', 'engine', 'engine_from_config', 'event', 'events', 'exc', 'except_', 'except_all', 'exists', 'extract', 'false', 'func', 'funcfilter', 
'insert', 'inspect', 'inspection', 'interfaces', 'intersect', 'intersect_all', 'join', 'lateral', 'literal', 'literal_column', 'log', 'modifier', 'not_', 'null', 'or_', 'outerjoin', 
'outparam', 'over', 'pool', 'processors', 'schema', 'select', 'sql', 'subquery', 'table', 'tablesample', 'text', 'true', 'tuple_', 'type_coerce', 'types', 'union', 'union_all', 
'update', 'util', 'within_group']

 

2.鏈接數據庫並查詢

from sqlalchemy import create_engine

#鏈接數據庫,生成engine對象;最大鏈接數爲5個
engine = create_engine("mysql+pymysql://root:root@127.0.0.1:3306/bigdata", max_overflow=5)
print(engine)   #Engine(mysql+pymysql://root:***@127.0.0.1:3306/bigdata)
result = engine.execute('select * from table1') #不用commit(),會自動commit
print(result.fetchall())

3.建立表

from sqlalchemy import Table, Column, Integer, String, MetaData
metadata = MetaData()  #至關於實例一個父類

user = Table('user', metadata,      #至關於讓Table繼承metadata類
             Column('id', Integer, primary_key=True),
             Column('name', String(20)),
             )

color = Table('color', metadata,    #表名color
              Column('id', Integer, primary_key=True),
              Column('name', String(20)),
              )
metadata.create_all(engine)  #table已經與metadate綁定

 

 

四、增刪改查

conn.execute解讀:session

    def execute(self, object, *multiparams, **params):
        r"""Executes a SQL statement construct and returns a
        :class:`.ResultProxy`.

        :param object: The statement to be executed.  May be
         one of:

         * a plain string
         * any :class:`.ClauseElement` construct that is also
           a subclass of :class:`.Executable`, such as a
           :func:`~.expression.select` construct
         * a :class:`.FunctionElement`, such as that generated
           by :data:`.func`, will be automatically wrapped in
           a SELECT statement, which is then executed.
         * a :class:`.DDLElement` object
         * a :class:`.DefaultGenerator` object
         * a :class:`.Compiled` object

        :param \*multiparams/\**params: represent bound parameter
         values to be used in the execution.   Typically,
         the format is either a collection of one or more
         dictionaries passed to \*multiparams::

             conn.execute(
                 table.insert(),
                 {"id":1, "value":"v1"},
                 {"id":2, "value":"v2"}
             )

         ...or individual key/values interpreted by \**params::

             conn.execute(
                 table.insert(), id=1, value="v1"
             )

         In the case that a plain SQL string is passed, and the underlying
         DBAPI accepts positional bind parameters, a collection of tuples
         or individual values in \*multiparams may be passed::

             conn.execute(
                 "INSERT INTO table (id, value) VALUES (?, ?)",
                 (1, "v1"), (2, "v2")
             )

             conn.execute(
                 "INSERT INTO table (id, value) VALUES (?, ?)",
                 1, "v1"
             )

         Note above, the usage of a question mark "?" or other
         symbol is contingent upon the "paramstyle" accepted by the DBAPI
         in use, which may be any of "qmark", "named", "pyformat", "format",
         "numeric".   See `pep-249 <http://www.python.org/dev/peps/pep-0249/>`_
         for details on paramstyle.

         To execute a textual SQL statement which uses bound parameters in a
         DBAPI-agnostic way, use the :func:`~.expression.text` construct.

        """
參數解讀

conn = engine.connect()
conn.execute(user.insert(), {'id': 20, 'name': 'wqbin'})
conn.execute(user.insert(), {'id': 21, 'name': 'wang'})
conn.execute(user.insert(), {'id': 25, 'name': 'wangyang'})
conn.execute(user.insert(), { 'name': 'wangquincy'})
conn.close()

 

 

conn = engine.connect()
conn.execute(user.delete().where(user.c.id== "21"))
conn.close()

 

 

conn = engine.connect()
# 將name=="wqbin"更改成"name=="wqbin123"···
conn.execute(user.update().where(user.c.name == 'wqbin').values(name='wqbin123'))
conn.execute("""update user set name='wangyang123' where name ='wangyang' """)
conn.close()

 

 

# 查詢  下面不能寫 sql = user.select... 會曝錯
sql = select([user, ]) 
sql = select([user.c.id, ])  
sql = select([user.c.name, color.c.name]).where(user.c.id==25)
sql = select([user.c.name]).order_by(user.c.name)
sql = user.select([user]).group_by(user.c.name)

result = conn.execute(sql)
print(result.fetchall())
conn.close()

 

 5. 經過SQLAlchemy的增刪改查(重要):

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

Base = declarative_base() # 生成一個SqlORM基類(已經封裝metadata)
# echo=True能夠查看建立表的過程
engine = create_engine("mysql+pymysql://root:root@localhost:3306/bigdata")


class Host(Base):
    __tablename__ = 'hosts'    # 表名爲host
    id       = Column(Integer, primary_key=True, autoincrement=True)
    hostname = Column(String(64), unique=True, nullable=False)
    ip_addr  = Column(String(128), unique=True, nullable=False)
    port     = Column(Integer, default=22)


Base.metadata.create_all(engine)     # 建立全部表結構

if __name__ == '__main__':
    # 建立與數據庫的會話sessionclass,注意,這裏返回給session的是個class類,不是實例
    SessionCls = sessionmaker(bind=engine)
    session = SessionCls()   # 鏈接的實例
    # 準備插入數據
    h1 = Host(hostname='hadoop01', ip_addr='192.168.154.201')     # 實例化(未建立)
    h2 = Host(hostname='hadoop02', ip_addr='192.168.154.202', port=24)
    h3 = Host(hostname='hadoop03', ip_addr='192.168.154.203', port=24)

    session.add(h1)   #也能夠用下面的批量處理
    session.add_all([h2,h3])
    h2.hostname='hadoop021'   #只要沒提交,此時修改也沒問題

    # 查詢數據,返回一個對象 .first()返回一個 .all()返回全部
    obj = session.query(Host).filter(Host.port == "24").first()
    print("-->", obj)

    obj=session.delete(obj) # 刪除行
    print("-->", obj)
    session.commit()  # 提交

 

 

 

六、外鍵關聯

engine = create_engine("mysql+pymysql://root:root@localhost:3306/bigdata", echo=True)

class Host(Base):
    __tablename__ = 'hosts'   #表名
    id = Column(Integer, primary_key=True, autoincrement=True) #默認自增
    hostname = Column(String(64), unique=True, nullable=False)
    ip_addr = Column(String(128), unique=True, nullable=False)
    port = Column(Integer, default=22)
    #外鍵關聯,主機與組名關聯,一個組對應多個主機
    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 Group2(Base):
    __tablename__ = "group2"
    id = Column(Integer,primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

Base.metadata.create_all(engine)  # 建立全部表結構==>若是存在,不會報錯,也不會更新表結構

if __name__ == '__main__':
    # 建立與數據庫的會話session class ,注意,這裏返回給session的是個class,不是實例
    SessionCls = sessionmaker(bind=engine)
    session = SessionCls()  #鏈接的實例

    session.commit() #提交
建立全部表結構==>若是存在,不會報錯,也不會更新表結構

==========刪除hosts從新運行======

 

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

Base = declarative_base()  # 生成一個SqlORM 基類(已經封閉metadata)
#echo=True能夠查看建立表的過程
engine = create_engine("mysql+pymysql://root:root@localhost:3306/bigdata", echo=True)

class Host(Base):
    __tablename__ = 'hosts'   #表名
    id = Column(Integer, primary_key=True, autoincrement=True) #默認自增
    hostname = Column(String(64), unique=True, nullable=False)
    ip_addr = Column(String(128), unique=True, nullable=False)
    port = Column(Integer, default=22)
    #外鍵關聯,主機與組名關聯,一個組對應多個主機
    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 Group2(Base):
    __tablename__ = "group2"
    id = Column(Integer,primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

Base.metadata.create_all(engine)  # 建立全部表結構==>若是存在,不會報錯,也不會更新表結構

if __name__ == '__main__':
    # 建立與數據庫的會話session class ,注意,這裏返回給session的是個class,不是實例
    SessionCls = sessionmaker(bind=engine)
    session = SessionCls()  #鏈接的實例
    h1 = Host(hostname='hadoop01', ip_addr='192.168.154.201')     # 實例化(未建立)
    h2 = Host(hostname='hadoop02', ip_addr='192.168.154.202', port=24)
    h3 = Host(hostname='hadoop03', ip_addr='192.168.154.203', port=24)
    session.add_all([h1,h2,h3])
    g1 = Group(name = "g1")
    g2 = Group(name = "g2")
    g3 = Group(name = "g3")
    g4 = Group(name = "g4")
    session.add_all([g1,g2,g3,g4])

    session.query(Host).filter(Host.hostname=="hadoop02").update({"port":23,"group_id":1})
    session.commit() #提交
    session.close()

 

 還發現一個問題,添加一個不存在值的外檢會報錯:

 

 

 能夠獲取已經關聯的group_id後,但如何獲取已關聯的組的組名??

print(h.group.name) #AttributeError:'Host'object has no attribute 'group'

由於Host類根本就沒有group屬性!!

解決方法:

from sqlalchemy.orm import relationship       #導入relationship
class Host(Base):
    __tablename__ = 'hosts'   #表名
    id = Column(Integer, primary_key=True, autoincrement=True) #默認自增
    hostname = Column(String(64), unique=True, nullable=False)
    ip_addr = Column(String(128), unique=True, nullable=False)
    port = Column(Integer, default=22)
    #外鍵關聯,主機與組名關聯
    group_id = Column(Integer, ForeignKey("group.id"))
    group = relationship("Group")

那雙向關聯也要在Group類增長:hosts = relationship("Host")

可是也有隻用一句代碼就實現雙向關聯:



七、合併查詢join

group=relationship("Group",backref="host_list")

其實還有聚合計算和多對多關聯,可是我認爲使用ORM操做這種計算過於複雜不如寫sql。。。。

相關文章
相關標籤/搜索