概述
本着磨刀不误砍柴工、高内聚低耦合的代码原则,带着心中向往的行云流水的敲代码向往,秉承绝不走回头路、我的代码我最屌的原则记录了此文。
对于数据库数据的操作,力要写一个万能、通用且高效率的数据库操作类。
话不多说,直接上代码:
import pymysql
class MysqlHelper:
def __init__(self):
self._user = db_user
self._password = db_pwd
self._charset = 'utf8'
self._port = port
self._host = db_host
self._db_name = db_name
self._conn = self.connect_mysql()
if self._conn:
self._cursor = self._conn.cursor()
def connect_mysql(self):
"""
连接数据库
:return:
"""
conn = pymysql.connect(host=self._host,
user=self._user,
passwd=self._password,
db=self._db_name,
port=self._port,
cursorclass=pymysql.cursors.DictCursor,
charset=self._charset,
)
return conn
def close(self):
"""
关闭数据库连接
:return:
"""
self._cursor.close()
self._conn.close()
def execute(self, *args, params=None):
"""
执行多条sql
:param params:
:param args:
:return:
"""
if params is None:
params = []
effect = 0
for sql in args:
num = self._cursor.execute(sql, params)
effect += num
self._conn.commit()
self.close()
return effect
def select_multi(self, *args, params=None):
"""
查询语句,可以执行多条查询
:param args:
:param params:
:return: 返回元祖res:结果,num查询出行数
"""
if params is None:
params = []
i = 1
res = {}
for sql in args:
num = self._cursor.execute(sql, params)
sql_results = self._cursor.fetchall()
res['result%s' % i] = sql_results
res['effect%s' % i] = num
i += 1
self.close()
return res
def select(self, sql, act='all', params=None):
"""
查询语句方法
:param act:
:param sql:
:param params:
:return: 返回字典res:结果,num查询出行数
"""
global res
if params:
pass
else:
params = []
if act == 'all':
num = self._cursor.execute(sql)
sql_results = self._cursor.fetchall()
res = {
'result': sql_results,
'effect': num
}
self.close()
return res
好了,以上代码就是一个基本上可以直接使用的数据库操作类了,简述来说呢就是select_multi方法用来执行多条查询sql语句,select方法来执行单条语句(区别不大,但是各有用处),execute方法来执行多条操作语句。
之所以写了一个select_multi方法和execute通用多条语句就是用来仿写数据库中的事务操作,减少了每条语句对于数据库的连接、访问、关闭的这一系列操作,提高了效率。
但是对于cursor().execute()方法又是比较特殊的。
一、问题所在
先粘一段源代码:
def execute(self, query, args=None):
"""Execute a query
:param str query: Query to execute.
:param args: parameters used with query. (optional)
:type args: tuple, list or dict
:return: Number of affected rows
:rtype: int
If args is a list or tuple, %s can be used as a placeholder in the query.
If args is a dict, %(name)s can be used as a placeholder in the query.
"""
while self.nextset():
pass
query = self.mogrify(query, args)
result = self._query(query)
self._executed = query
return result
可以看出来execute方法里面有两个参数而对于args是非必传,查看过文档,在调用时的param参数在使用时是用来避免sql注入而存在的,这样就能避免了恶意攻击数据库等行为。但是了解过这点之后,在某些sql语句中例如
DATE_FORMAT(CURRENT_DATE,'%Y-%m-%d')
日期函数中的%Y却又会报错,网上很多方法都是将单%变成双%避免被认定为占位符而存在。
二、水落石出
对于Python中的三引号,将单%变为双%在更新之后又不好用了,会直接认定为是字符串%%,好坑......
于是再次观察源代码在execute()中的mogrify()方法
def mogrify(self, query, args=None):
"""
Returns the exact string that is sent to the database by calling the
execute() method.
This method follows the extension to the DB API 2.0 followed by Psycopg.
"""
conn = self._get_db()
if PY2: # Use bytes on Python 2 always
query = self._ensure_bytes(query, encoding=conn.encoding)
if args is not None:
query = query % self._escape_args(args, conn)
return query
传入了上层函数传来的args参数、而在函数中对于args进行了一次判定,而对于避免sql注入而传入的param参数,尽管为空,但是是一个空列表而非None对象,所以会卡在self._escape_args(args, conn)方法上,结果清晰明了了。
那么,改进!
def select(self, sql, act='all'):
"""
查询语句方法
:param act:
:param sql:
:param params:
:return: 返回字典res:结果,num查询出行数
"""
global res
if act == 'all':
num = self._cursor.execute(sql)
sql_results = self._cursor.fetchall()
res = {
'result': sql_results,
'effect': num
}
self.close()
return res
这样就通用的多了,舒服。当然,也可以自己加很多种操作方法,那就取决于你啦。玩的开心。