數據庫編程概述、pymysql基本操作方法總結、參數化列表防止SQL注入總結
2.6 Python數據庫編程
學習目標
? 1. 能夠說出數據庫編程的5個步驟
? 2. 能夠使用python從數據庫取出數據操作
--------------------------------------------------------------------------------
2.6.1 數據庫編程概述
數據庫編程就是針對數據庫的操作,通過編寫程序的方式,讓程序做為數據庫的客戶端進行數據庫操作。
通過使用程序代碼的方式去連接數據庫服務器,通過和服務器進行交互完成對數據庫的增刪改查的方式,就稱為數據庫編程。
數據庫概念包含 數據庫文件、服務器和數據庫客戶端,pymysql就是一種客戶端。
? pymysql的安裝:
? 1. 聯網pip命令安裝
? ? ? a. pip3 install PyMySQL
? ? ? b. 在使用該命令的時候可能會提示需要管理員權限進行安裝,只需要在前面加上sudo即可;
? 2. 使用 git 命令下載安裝包安裝(你也可以手動下載):
? ? ? a. git clone https://github.com/PyMySQL/PyMySQL
? ? ? b. cd PyMySQL/
? ? ? c. python3 setup.py install
? ? ? ? ? i. 如果有安裝包直接切換路徑,和setup.py同級,再安裝
? 1>pymysql基本操作方法總結:
? ? ? 1>Python 中操作數據庫,要經過五個步驟
? ? ? ? ? ■ 1.連接數據庫,2.獲取游標,3.數據庫操作,4.關閉游標,5.關閉數據庫連接
? ? ? 2>數據庫對象
? ? ? ? ? ■ 獲取游標
? ? ? ? ? ? ? cursor()
? ? ? ? ? ■ 提交數據
? ? ? ? ? ? ? commit()
? ? ? ? ? ■ 撤銷操作
? ? ? ? ? ? ? rollback()
? ? ? ? ? ■ 關閉連接
? ? ? ? ? ? ? close()
? ? ? 3>游標對象
? ? ? ? ? ■ 執行SQL語句
? ? ? ? ? ? ? execute()
? ? ? ? ? ■ 獲取一條查詢 結果
? ? ? ? ? ? ? fetchone()
? ? ? ? ? ■ 獲取指定條數查詢結果
? ? ? ? ? ? ? fetchmany(n)
? ? ? ? ? ■ 獲取所有查詢 結果
? ? ? ? ? ? ? fetchall()
? ? ? ? ? ■ 關閉游標
? ? ? ? ? ? ? close()
? 2.參數化列表防止SQL注入總結
? ? ? 1>什么是SQL注入?
? ? ? ? ? ■ 是指在連接SQL語句時,用戶傳入了非法的數據,使SQL語句的意義發生變化,導至數據泄露
? ? ? 2>產生原因:
? ? ? ? ? ■ 后臺對用戶提交的帶有惡意的數據和 SQL 進行字符串方式的拼接,得到了脫離原意的 SQL 語句,從而影響了 SQL 語句的語義,最終產生數據泄露的現象。
? ? ? 3>如何防止:
? ? ? ? ? ■ SQL 語句的參數化, 將 SQL 語句的所有數據參數存在一個列表中傳遞給 execute 函數的第二個參數
? ? ? 4>注意:
? ? ? ? ? ■ sql語句中 -- 代表注釋的意思
? ? ? ? ? ■ 此處不同于python的字符串格式化,必須全部使用%s占位
2.6.2 Python 中操作 MySQL 步驟
在 Python 中操作數據庫,要經過五個步驟,分別是連接數據庫,獲取游標,數據庫操作,關閉游標,關閉數據庫連接。
這個過程和實際倉庫操作相同,比如現在要去一個實際的倉庫。
2.6.3 pymysql 操作
在 Pyhton 中,使用 Pymysql 模塊來對數據庫進行編程。
? 導入模塊(connect = Connection = Connect)
? ? ? ? from pymysql import Connect
? <1>創建連接對象 conn=Connect(參數列表)
? ? ? 目的:用于建立代碼與數據庫的連接
? ? ? * 參數host:連接的mysql主機,如果本機是'localhost'
? ? ? * 參數port:連接的mysql主機的端口,默認是3306
? ? ? * 參數database:數據庫的名稱
? ? ? * 參數user:連接的用戶名
? ? ? * 參數password:連接的密碼
? ? ? * 參數charset:通信采用的編碼方式,推薦使用utf8
? <2>通過連接對象獲取游標對象 cur = conn.cursor()
? ? ? 創建Cursor游標對象 目的: 用于執行sql語句并獲得結果
? <3>使用游標執行SQL語句 cur.execute(operation , [parameters])
? ? ? ? 1>sql_str = '''select * from students;'''
? ? ? ? ? ■ 注意:以字符串形式書寫SQL語句,因為SQL語句中也會出現字符串,所以建議使用三引號 ``` 引號形式將SQL詩句引起來
? ? ? 2>執行SQL語句row_count = cur.execute(sql_str)
? ? ? ? ? ■ 注意:執行SQL語句,返回受影響的行數,主要用于執行insert、update、delete語句
? ? ? ? ? ? ? 打印row_count輸出受影響的行數
? ? ? 3>取出結果:
? ? ? ? ? ■ 注意:因為在獲取數據時,游標是移動的,所以前面取過的數據,后面不會再取了。
? ? ? ? ? ■ 1.獲取結果集中的一條 row_one = cur.fetchone()
? ? ? ? ? ? ? 返回一個元組 如 (1,'妲己',18)
? ? ? ? ? ■ 2.獲取結果集中指定條數 row_many = cur.fetchmany(2)
? ? ? ? ? ? ? 返回一個元組 如 ((1,'妲己',18),(2,'公孫離',20))
? ? ? ? ? ? ? cur.fetchmany(n)獲取結果集中的n條,返回一個元組
? ? ? ? ? ■ 3.獲取結果集中的所有 row_all = cur.fetchall()
? ? ? ? ? ? ? 執行查詢時,獲取結果集的所有行,一行構成一個元組,再將這些元組裝入一個元組返回. 如((1,'妲己',18),(2,'公孫離',20),(3,'姜子牙',28))
? ? ? 4>提交數據 conn.commit()
? ? ? ? ? ■ 撤銷數據 conn.rollback()
? <4>關閉游標 cur.close()
? <5>關閉連接 conn.close()
2.6.4 使用 pymsql 完成數據查詢
? 準備數據
? ? ? 1>創建數據庫
? ? ? ? ? ■ create database python_db charset=utf8;
? ? ? 2>使用數據庫
? ? ? ? ? ■ use python_db;
? ? ? 3>students表
? ? ? ? ? ■ 注意:unsigned表示無符號的意思,也就是非負數,只用于整型
? ? ? ? ? ■ create table students(
? ? id int unsigned primary key auto_increment not null,
? ? name varchar(20) default '',
? ? age tinyint unsigned default 0,
? ? height decimal(5,2),
? ? gender enum('男','女','中性','保密') default '保密',
? ? cls_id int unsigned default 0,
? ? is_delete int default 0);
? ? ? 4>classes表
? ? ? ? ? ■ create table classes (
? ? id int unsigned auto_increment primary key not null,
? ? name varchar(30) not null);
? ? ? 5>向students表中插入數據
? ? ? ? ? ■ insert into students values
(0,'小明',18,180.00,2,1,0),
(0,'小月月',18,180.00,2,2,1),
(0,'彭于晏',29,185.00,1,1,0),
(0,'劉德華',59,175.00,1,2,1),
(0,'黃蓉',38,160.00,2,1,0),
(0,'鳳姐',28,150.00,4,2,1),
(0,'王祖賢',18,172.00,2,1,1),
(0,'周杰倫',36,NULL,1,1,0),
(0,'程坤',27,181.00,1,2,0),
(0,'劉亦菲',25,166.00,2,2,0),
(0,'金星',33,162.00,3,3,1),
(0,'靜香',12,180.00,2,4,0),
(0,'郭靖',12,170.00,1,4,0),
(0,'周杰',34,176.00,2,5,0);
? ? ? 6>向classes表中插入數據
? ? ? ? ? ■ insert into classes values (0, "python_01期"), (0, "python_02期");
? 查詢數據
# 導入模塊
from pymysql import connect
# 連接數據庫
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# 獲取游標
cur = conn.cursor()
# 以字符串形式書寫SQL語句,因為SQL語句中也會出現字符串,所以建議使用 ``` 引號形式將SQL詩句引起來
sql_str = '''select * from students;'''
# 執行SQL語句
row_count = cur.execute(sql_str)
# 顯示執行 SQL 語句影響的行數
print(row_count)
# 獲取一條記錄
row_one = cur.fetchone()
# 顯示獲取的記錄
print(row_one)
# 獲取多條記錄
row_many = cur.fetchmany(4)
# 遍歷輸出所有的結果
for t in? row_many:
? ? print(t)
# 獲取所有的數據
row_all = cur.fetchall()
# 遍歷輸出所有的結果
for t in? row_all:
? ? print(t)
# 關閉游標
cur.close()
# 關閉數據庫
conn.close()
注意:因為在獲取數據時,游標是移動的,所以前面取過的數據,后面不會再取了。
? 增刪改
# 導入模塊
from pymysql import connect
# 連接數據庫
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# 獲取游標
cur = conn.cursor()
# 以字符串形式書寫SQL語句
# sql_str = '''insert into students values(0,'新來的',20,180,'男',1,1)'''
# sql_str = '''update students set name = '王鋼蛋' where name = '新來的'; '''
sql_str = '''delete from students where name='王鋼蛋'; '''
# 執行SQL語句
row_count = cur.execute(sql_str)
# 在執行增刪改操作時,需要向數據庫提交操作,否則操作不成功
conn.commit()
# 關閉游標
cur.close()
# 關閉數據庫
conn.close()
? 回滾(取消操作)
# 導入模塊
from pymysql import connect
# 連接數據庫
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# conn.autocommit(True)
# 獲取游標
cur = conn.cursor()
# 以字符串形式書寫SQL語句
sql_str = '''insert into students values(0,'新來的',20,180,'男',1,1)'''
#插入10條數據
for i in range(10):
? ? # 執行SQL語句
? ? row_count = cur.execute(sql_str)
# 在執行增刪改操作時,如果不想提交前面的修改操作,可以使用 rollback 回滾取消操作
conn.rollback()
# 關閉游標
cur.close()
# 關閉數據庫
conn.close()
2.6.5 參數化列表防止SQL注入
? 什么是SQL注入?
? ? ? 是指在連接SQL語句時,用戶傳入了非法的數據,使SQL語句的意義發生變化,導至數據泄露
? 產生原因:
? ? ? 后臺對用戶提交的帶有惡意的數據和 SQL 進行字符串方式的拼接,得到了脫離原意的 SQL 語句,從而影響了 SQL 語句的語義,最終產生數據泄露的現象。
? 如何防止:
? ? ? SQL 語句的參數化, 將 SQL 語句的所有數據參數存在一個列表中傳遞給 execute 函數的第二個參數
? 注意:
? ? ? sql語句中 -- 代表注釋的意思
? ? ? 此處不同于python的字符串格式化,必須全部使用%s占位
? # 導入模塊
? from pymysql import connect
? find_name = input("請輸姓名:")
? # 連接數據庫
? conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123', charset='utf8')
? # 獲得Cursor對象
? cur = conn.cursor()
? # # # 非安全的方式
? # # # 輸入 "小明" or 1
? # sql = '''select * from students where name=%s''' % find_name
? # print("""sql===>%s<====""" % sql)
? # # 執行select語句,并返回受影響的行數:查詢所有數據
? # count = cur.execute(sql)
? # 安全的方式
? # 構造參數列表
? params = [find_name]
? sql = '''select * from students where name=%s;'''
? print("""sql===>%s<====""" % sql)
? # 執行select語句,execute方法在內部實現了防SQL注入的功能,但具體如何實現并不清楚,隱藏了細節
? count = cur.execute(sql, params)
? # 注意:
? # 如果要是有多個參數,需要進行參數化
? # 那么params = [數值1, 數值2....],此時sql語句中有多個%s即可
? # 打印受影響的行數
? print(count)
? # 獲取查詢的結果
? result = cur.fetchall()
? # 打印查詢的結果
? print(result)
? # 關閉Cursor對象
? cur.close()
? # 關閉Connection對象
? conn.close()
2.6.6 數據庫編程練習
? 準備數據
? ? ? 創建數據庫
? ? ? ? ? ■ create database JDDB charset=utf8;
? ? ? ? ? ■ use JDDB
? ? ? 導入數據
? ? ? ? ? ■? source JDDB.sql
? 代碼實現
? ? ? 1>判斷當前是否是主程序
if __name__ == '__main__':
main()
? 2>實現主函數
def main():
# 創建 JD 類的對象
jd = JD()
# 運行 run 方法
jd.run()
? 3>實現 JD 類 因為所有的操作都要操作數據庫,為了避免代碼冗余,所以將連接和關閉操作設計到類中
? 對象創建成功,數據庫就連接成功,對象銷毀時,數據庫關閉
class JD(object):
"""JD 類,提供商品查詢服務"""
# 將數據庫連接操作放到初化方法中,對象創建時,自動連接數據庫
def __init__(self):
# 連接數據庫
self.__conn = connect(host='localhost', port=3306, database='JDDB', user='root', password='123123', charset='utf8')
# 獲取游標
self.__cur = self.__conn.cursor()
# 將數據庫關閉操作放到 __del__方法中,當對象銷毀時,自動關閉數據庫
def __del__(self):
# 關閉游標
self.__cur.close()
# 關閉數據庫
self.__conn.close()
? 4>實現 run 方法 因為需要重復選擇,所以要用死循環
# run 方法,提供顯示接口
def run(self):
while True:
? ? print("1查詢所有商品信息")
? ? print("2查詢所有商品在種類信息")
? ? print("3查詢所有商品在品牌信息")
? ? print("4添加商品種類")
? ? print("5根據id查詢商品信息")
? ? print("6根據id查詢商品信息安全方式")
? ? selectID = input('請輸入要執行的功能編號:')
? ? if selectID == '1':
? ? ? ? # 查詢所有商品信息
? ? ? ? self.fetch_all_info()
? ? elif selectID == '2':
? ? ? ? # 查詢種類信息
? ? ? ? self.fetch_cate()
? ? elif selectID == '3':
? ? ? ? # 查詢品牌信息
? ? ? ? self.fetch_brand()
? ? elif selectID == '4':
? ? ? ? # 添加一個商品類型
? ? ? ? self.add_info()
? ? elif selectID == '5':
? ? ? ? # 通過ID 查找商品
? ? ? ? self.find_info()
? ? elif selectID == '6':
? ? ? ? # 通過ID 查找商品 防SQL注入
? ? ? ? self.find_info_safe()
? ? else:
? ? ? ? print('輸入行號不正確!')
? 5>實現一個顯示方法,用來輸出結果
# 用來顯示結果的方法,私有,對外不可見
def __show_result(self, result):
for t in result:
? ? print(t)
? 6>實現查詢所有商品方法
# 查詢所有商品信息
def fetch_all_info(self):
sql_str = ''' select * from goods;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
? 7>查詢種類信息
# 查詢種類信息
def fetch_cate(self):
sql_str = ''' select * from goods_cates;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
? 8>查詢品牌信息
# 查詢品牌信息
def fetch_brand(self):
sql_str = ''' select * from goods_brands;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
? 9>添加商品類型
# 添加商品類型
def add_info(self):
new_type = input('請輸入商品類型:')
sql_str = ''' insert into goods_cates values(0,"%s");'''% new_type #注意,這里占位符要加引號
self.__cur.execute(sql_str)
self.__conn.commit() # 修改操作要手動提交
? 10>根據id 查找商品
# 根據id查找商品
def find_info(self):
id = input('請輸入ID:')
sql_str = ''' select * from goods where id=%s;'''%id
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
? 11>根據id 查找商品,案例防注入
# 根據id查找商品,案例防SQL注入
def find_info_safe(self):
id = input('請輸入ID:')
sql_str = ''' select * from goods where id=%s;'''
self.__cur.execute(sql_str,(id,))
self.__show_result(self.__cur.fetchall())