關於db訪問層的封裝設計感想 dbpy項目的開發

dbpy

dbpy是一個python寫的數據庫CURD人性化api庫。借鑑了 webpy db 和 drupal database 的設計。 若是喜歡 tornado db 或者 webpy db這類輕巧的db庫,或者想發揮原生SQL優點,那麼值得一試。css

Featues

  1. 靈活簡單
  2. 天馬行空的SQL構建語法糖
  3. 線程安全的鏈接池
  4. 支持讀寫分離(當前限定只能是一主多副模式)
  5. 支持簡單事務

Install

從github上fork下來,終端執行下面命令:python

cd dbpy # the path to the project python setup.py install

Notemysql

安裝前先安裝 MySQLdb (MySQL-python) 依賴python庫git

Development

下載後終端執行:github

cd dbpy # the path to the project python setup.py develop

Compatibility

在 Python 2.7.x 測試開發web

DB API

先提醒下模塊使用單例模式。因此api相對比較好使。sql

config = {
        'passwd': 'test',
        'user': 'test',
        'host': 'localhost',
        'db': 'test',
        'max_idle' : 5*60
    }

db.setup(config,  minconn=5, maxconn=10,
    adapter='mysql', key='defalut', slave=False)

 

setup

config: 是數據庫鏈接參數,能夠傳入MySQLDB#connect接口中全部的可選參數。 其中``max_idle`` 相對是mysql服務端 connect_timeout配置,默認10秒。
minconn: 爲當前數據庫鏈接池保持最小鏈接池,默認爲5
maxconn: 爲當前數據庫鏈接池最大鏈接池,默認爲10
adapter: 爲適配器名,當前只支持 mysql
key: 是數據庫的標識符,默認爲 default
slave: 若是爲true那麼當前的數據庫將會註冊爲讀數據庫。若是你沒有作讀寫分離,只有一個數據庫用來讀寫,那麼setup一次就好,這樣就能夠讀寫。
config = {
        'passwd': 'test',
        'user': 'test',
        'host': 'localhost',
        'db': 'test',
        'max_idle' : 5*60
    }

db.setup(config, key='test')
config['host'] = 'test.slave'
# 此次setup將會把key標記爲僅可寫,就是在後面用api時,制定到當前key的數據庫會作數據分離
db.setup(config, key='test', slave=True)

config['host'] = 'test.slave2'
# 再加入一個slave數據庫
db.setup(config, key='test', slave=True)


config['host'] = 'host2'
config['db'] = 'social'
# 再加入一個數據庫
db.setup(config, key='social', slave=True)

 

query

query用於raw sql的查詢語言。若是有更新數據請用execute.數據庫

query(sql, args=None, many=None, as_dict=False, key='default'):api

sql: mysql的格式化raw sql
args: 能夠爲元組和list,是sql格式化預處理的輸入
many: 若是指定爲大於零的整數將會使用fetchmany語句,並返回對象將會是迭代器.不然api調用fetchall返回結果.
as_dict: 若是爲 true將會返回字典行,不然返回元組行。
key: 用於指定使用那個數據庫。
print db.query('SELECT 1')
# > ((1L,),)

# use social db
print db.query('SELECT 1', key='social')
# > ((1L,),)

print db.query('SELECT * FROM users WHERE uid=%s and name=%s', (1, 'user_1'))
# > ((1L, u'user_1'),)

# Wanna return dict row
print db.query('SELECT * FROM users WHERE uid=%s and name=%s',
            (1, 'user_1'), as_dict=True)
# > ({'uid': 1L, 'name': u'user_1'},)

# Use fetchmany(many) then yeild, Return generator
res = db.query('SELECT * FROM users WHERE uid=%s and name=%s',
                (1, 'user_1'), many=5, as_dict=True)
print res
print res.next()
# > <generator object _yield at 0x7f818f4b6820>
# > {'uid': 1L, 'name': u'user_1'}

 

execute

execute用於raw sql的更新語言。 execute(sql, args=None, key='default'):

sql: mysql的格式化raw sql
args: 能夠爲元組和list,是sql格式化預處理的輸入.以下面例子insert語句values有多個插入時,調用 executemany
key: 用於指定使用那個數據庫。

返回規範:

對於insert 將會返回 last_insert_id, 其餘更新語句返回rowcount
db.execute('DROP TABLE IF EXISTS `users`')
db.execute("""CREATE TABLE `users` (
         `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `name` varchar(20) NOT NULL,
        PRIMARY KEY (`uid`))""")

# insert語句插入多個value,注意這樣寫將會調用executemany,你懂的,就是封裝了多條execute的玩意
db.execute('INSERT INTO users VALUES(%s, %s)', [(10, 'execute_test'), (9, 'execute_test')])
# > 9
db.execute('DELETE FROM users WHERE name=%s', ('execute_test',))
# > 2


# use social db
db.execute('delete from events where created_at<%s', (expired, ), key='social')
# > 10

 

select

select用於構建select 查詢語言。

select(table, key='default'):

table: 選定表
key: 用於指定使用那個數據庫。

select all

db.select('users')
# > SELECT * FROM `users`

 

specific columns

db.select('users').fields('uid', 'name')
# > SELECT `uid`, `name` FROM `users`

 

execute

在構建好查詢條語句後使用execute api能夠返回結果。

execute(many=None, as_dict=False):

many: 若是指定爲大於零的整數將會使用fetchmany語句,並返回對象將會是迭代器.不然api調用fetchall返回結果.
as_dict: 若是爲 true將會返回字典行,不然返回元組行。
q = db.select('users').fields('uid', 'name')
res = q.execute()
print res
# > ((1L, u'user_1'), (2L, u'user_2'), (3L, u'user_3'), (4L, u'user_4'), (5L, None))

res = q.execute(many=2, as_dict=True)
print res
print res.next()
# > <generator object _yield at 0x7f835825e820>
# > {'uid': 1L, 'name': u'user_1'}

 

Condition

上面已經學會如何作簡單的查詢,那麼如何組件條件查詢。這裏將會重點講述condition方法如何構建各類查詢條件。

condition(field, value=None, operator=None):

field: 是條件限制的表字段
value: 是字段的條件值, 若是炸路額, oprator都不指定就是 "field is null"
operator: 默承認能是等於操做符號, 可選的操做符號有 BETWEEN, IN, NOT IN, EXISTS, NOT EXISTS, IS NULL, IS NOT NULL, LIKE, NOT LIKE, =, <, >, >=, <=, <>等

在全部的select,update, delete查詢中多個默認的condition將會是and條件組合。

simple
db.select('users').condition('uid', 1) # condition('uid', 1, '=')
# > SELECT * FROM `users`
# > WHERE  `uid` = %s

 

in
db.select('users').condition('uid', (1, 3)) # condition('uid', [1, 3]) 同樣
# > SELECT * FROM `users`
# > WHERE  `uid` IN  (%s, %s)

 

between
 
 
db.select('users').condition('uid', (1, 3), 'between')
# > SELECT * FROM `users`
# > WHERE  `uid` BETWEEN %s AND %s
 
 

 

multi condition
db.select('users').condition('uid', (1, 3), 'between')
# > SELECT * FROM `users`
# > WHERE  `uid` BETWEEN %s AND %s

 

or condition
or_cond = db.or_().condition('uid', 1).condition('name', 'blabla')
db.select('users').condition(or_cond).condition('uid', 1, '<>')
# > SELECT * FROM `users`
# > WHERE  ( `uid` = %s OR `name` = %s ) AND `uid` <> %s

 

order by

db.select('users').order_by('name')
# > SELECT * FROM `users`
# > ORDER BY `name`

db.select('users').order_by('name', 'DESC')
# > SELECT * FROM `users`
# > ORDER BY `name` DESC

db.select('users').order_by('name', 'DESC').order_by('uid')
# > SELECT * FROM `users`
# > ORDER BY `name` DESC, `uid`

 

distinct

db.select('users').order_by('name')
# > SELECT * FROM `users`
# > ORDER BY `name`

db.select('users').order_by('name', 'DESC')
# > SELECT * FROM `users`
# > ORDER BY `name` DESC

db.select('users').order_by('name', 'DESC').order_by('uid')
# > SELECT * FROM `users`
# > ORDER BY `name` DESC, `uid`

 

group by

db.select('users').group_by('name', 'uid')
# > SELECT * FROM `users`
# > GROUP BY `name`, `uid`

 

limit and offset

db.select('users').limit(2).offset(5) # > SELECT * FROM `users` # > LIMIT 2 OFFSET 5

null condition

db.select('users').is_null('name').condition('uid', 5)
# > SELECT * FROM `users`
# > WHERE  `name` IS NULL  AND `uid` = %s

db.select('users').is_not_null('name').condition('uid', 5)
# > SELECT * FROM `users`
# > WHERE  `name` IS NOT NULL  AND `uid` = %s

db.select('users').condition('name', None)
# > SELECT * FROM `users`
# > WHERE  `name` IS NULL

 

complex conditions

使用 db.and_(), db.or_() 能夠構建and或or粘合的條件組合。

or_cond = db.or_().condition('field1', 1).condition('field2', 'blabla')
and_cond = db.and_().condition('field3', 'what').condition('field4', 'then?')
print db.select('table_name').condition(or_cond).condition(and_cond)

# > SELECT * FROM `table_name`
# > WHERE  ( `field1` = %s OR `field2` = %s ) AND ( `field3` = %s AND `field4` = %s )

 

expr

若是你須要使用 count sum之類的集聚函數,那麼使用 Expr構建字段吧。

from  db import expr

db.select('users').fields(expr('count(*)'))
# > SELECT count(*) FROM `users`

db.select('users').fields(expr('count(uid)', 'total'))
# > SELECT count(uid) AS `total` FROM `users`

 

insert

insert用於構建insert into的sql語句。

insert(table, key='default'):

table: 選定表
key: 用於指定使用那個數據庫。
q = db.insert('users').values((10, 'test_insert'))
# > INSERT INTO `users` VALUES(%s, %s)
print q._values
# > [(10, 'test_insert')]


q = db.insert('users').fields('name').values({'name': 'insert_1'}).values(('insert_2',))
# > INSERT INTO `users` (`name`) VALUES(%s)
print q._values
# > [('insert_1',), ('insert_2',)]

 

構建好執行execute會執行數據庫插入,execute返回的是last insert id:

print q.execute()
# > 2

 

update

update用於構建update的sql語句

update(table, key='default'):

table: 選定表
key: 用於指定使用那個數據庫。

update 主要可用的方法是mset和set, mset:

mset: 傳入的是字典,用於一次set多個表屬性
set(column, value): 只能設置一個屬性,能夠屢次使用

構建條件codition前面已經講述了。請參考 select

db.update('users').mset({'name':None, 'uid' : 12}).condition('name','user_1')
# > UPDATE `users` SET `name` = %s, `uid` = %s WHERE  `name` = %s

q = (db.update('users').set('name', 'update_test').set('uid', 12)
    .condition('name', 'user_2').condition('uid', 2)) # .execute()
print q.to_sql()
# > UPDATE `users` SET `name` = %s, `uid` = %s WHERE  `name` = %s AND `uid` = %s

 

構建好執行execute會執行數據庫插入,execute返回的是更新的 rowcount:

print q.execute()
# > 2

 

limit

由於你可能但願限制更新幾條。那麼可使用limit

 
 
db.update('users').mset({'name':None, 'uid' : 12}).condition('name','user_1').limit(5)
# > UPDATE `users` SET `name` = %s, `uid` = %s WHERE  `name` = %s  LIMIT 5
 
 

 

delete

delete 用於構建delete from的sql語句。

delete(table, key='default'):

table: 選定表
key: 用於指定使用那個數據庫。

構建條件codition前面已經講述了。請參考 select

db.delete('users').condition('name','user_1')
# > DELETE FROM `users` WHERE  `name` = %s

 

構建好執行execute會執行數據庫插入,execute返回的是刪除的 rowcount:

print q.execute()
# > 2

 

to_sql and str

db.insertdb.updatedb.delete 返回的對象均可以使用 to_sql 或者__str__ 來查看構建成的sql語句。

q = (db.update('users').set('name', 'update_test').set('uid', 12)
        .condition('name', 'user_2').condition('uid', 2))
print q.to_sql()
print q
# > UPDATE `users` SET `name` = %s, `uid` = %s WHERE  `name` = %s AND `uid` = %s

 

transaction

transaction(table, key='default'):

table: 選定表
key: 用於指定使用那個數據庫。

對於事務,這裏比較簡單的實現。要麼所有執行,要麼所有不作,沒有作保存點。

# with context
with db.transaction() as t:
    t.delete('users').condition('uid', 1).execute()
    (t.update('users').mset({'name':None, 'uid' : 12})
        .condition('name','user_1').execute())


# 普通用法
t = db.transaction()
t.begin()
t.delete('users').condition('uid', 1).execute()
(t.update('users').mset({'name':None, 'uid' : 12})
    .condition('name','user_1').execute())

#這裏將會提交,若是失敗將會rollback
t.commit()

 

Note

使用 begin必定要結合commit方法,否則可能鏈接不會返還鏈接池。建議用 with 語句。

simple orm

這裏將會講述最簡單的orm構建技巧, 詳細參考 samples

import model
from orm import Backend
import db

db.setup({ 'host': 'localhost', 'user': 'test', 'passwd': 'test', 'db': 'blog'})


user = Backend('user').find_by_username('username')
if user and user.check('password'):
    print 'auth'

user = model.User('username', 'email', 'real_name', 'password',
        'bio', 'status', 'role')
if Backend('user').create(user):
    print 'fine'

user = Backend('user').find(12)
user.real_name = 'blablabla....'
if Backend('user').save(user):
    print 'user saved'

if Backend('user').delete(user):
    print 'delete user failed'


post = model.Post('title', 'slug', 'description', 'html', 'css', 'js',
        'category', 'status', 'comments', 'author')
if not Backend('post').create(post):
    print 'created failed'

 

Future

當前只支持mysql適配驅動,由於我的並不熟悉其餘關聯數據庫,dbpy的設計比較靈活,因此若是有高手能夠嘗試寫寫其餘數據庫適配,仿照 db/mysql目錄 若是寫pgsql的適配應該不會多餘800行代碼。

對於構建orm框架方面,從我的來說,更喜歡原生SQL,也不打算再造一個orm輪子。從設計和實現來講,dbpy是爲了更好的發揮原生SQL優點和簡單靈活。

我的一些想法:

  1. 爲select加入join構建方法糖。
  2. 嘗試完成schema類,用於建立表,修改表結構等。
  3. 加入一些mysql特有的sql方法糖,好比replace, on dup更新等。
  4. 優化改進鏈接池,好比加入固定數量鏈接的鏈接池。
相關文章
相關標籤/搜索