【13.0】PyMySQL模块

【一】Python操作数据库简介

【1】什么是DB-API

  • Python标准数据库规范为 DB-API, DB-API定义了一系列必须的对象和数据库操作方式,以便为各种数据库系统和数据库访问程序提供一致的访问接口。

image-20240120124726270

【2】数据库操作模块

  • 开发人员将接口封装成不同的数据库操作模块,不同的数据库需要不同数据库操作模块
  • 例如,MySQL数据库,它对应以下操作模块:https://wiki.python.org/moin/MySQL

image-20240120124916725

  • 最常用的应该是
    • MySQL-python:也就是MySQLdb,底层是通过C操作MySQL,效率高,但是只支持py2,不支持py3。
    • mysqlclient:是MySQL-python的一个分支。它增加了Python 3支持,并修复了许多错误。Django文档推荐的MySQL依赖库。
    • PyMySQL:纯Python实现的模块,可以与Python代码兼容衔接,并也几乎兼容MySQL-python。
    • MySQL Connector/Python:MySQL官方推出的纯Python实现的模块。

【二】PyMySQL简介

  • 纯Python实现的模块,可以与Python代码兼容衔接,并也几乎兼容MySQL-python。
  • 遵循 Python 数据库 API v2.0 规范。
  • 官网:https://zetcode.com/python/pymysql/

【三】PyMySQL使用

【0】安装

pip install pymysql

【1】连接数据库

  • 使用connect函数创建连接对象,此连接对象提供关闭数据库、事务提交、事务回滚等操作。

  • 传入参数有很多,具体参考文档,一般参数基本连接信息 host, user, password, port(默认为3306), database。

(1)连接语法

# 导入pymysql模块
import pymysql

# 创建连接对象
conn = pymysql.connect(user='root',  # 必填参数:数据库连接用户名
                       password='password',  # 必填参数:数据库连接密码
                       host='127.0.0.1',  # 必填参数:数据库服务器地址,本地可以填 localhost 或 127.0.0.1
                       port=3306,  # 必填参数:MySQL数据库端口,默认是3306
                       database='test'  # 必填参数:需要链接那个数据库
                       )
def __init__(
        self,
        *,
        user=None,  # The first four arguments is based on DB-API 2.0 recommendation.
        password="",
        host=None,
        database=None,
        unix_socket=None,
        port=0,
        charset="",
        collation=None,
        sql_mode=None,
        read_default_file=None,
        conv=None,
        use_unicode=True,
        client_flag=0,
        cursorclass=Cursor,
        init_command=None,
        connect_timeout=10,
        read_default_group=None,
        autocommit=False,
        local_infile=False,
        max_allowed_packet=16 * 1024 * 1024,
        defer_connect=False,
        auth_plugin_map=None,
        read_timeout=None,
        write_timeout=None,
        bind_address=None,
        binary_prefix=False,
        program_name=None,
        server_public_key=None,
        ssl=None,
        ssl_ca=None,
        ssl_cert=None,
        ssl_disabled=None,
        ssl_key=None,
        ssl_verify_cert=None,
        ssl_verify_identity=None,
        compress=None,  # not supported
        named_pipe=None,  # not supported
        passwd=None,  # deprecated
        db=None,  # deprecated
)

【2】主要方法介绍

方法 功能
cursor() 获取游标对象,操作数据库,如执行DML操作,调用存储过程等
commit() 提交事务
rollback() 回滚事务
close() 关闭数据库连接

【3】操作MySQL数据库

(1)创建游标对象

import pymysql
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='123456',
    port=3306,
    database='data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(pymysql.cursors.DictCursor)

(2)执行SQL语句

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

sql = 'select * from emp limit 10'
cursor.execute(sql)

【4】查询结果

(1)获取所有结果(fetchall)

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

sql = 'select * from emp limit 10'
cursor.execute(sql)
result = cursor.fetchall()  # 返回所有数据
print(result)
[{'id': 1, 'name': 'dream', 'sex': 'male', 'age': 78, 'hire_date': datetime.date(2022, 3, 6), 'post': '陌夜痴梦久生情', 'post_comment': None, 'salary': 730.33, 'office': 401, 'depart_id': 1}, {'id': 2, 'name': 'mengmeng', 'sex': 'female', 'age': 25, 'hire_date': datetime.date(2022, 1, 2), 'post': 'teacher', 'post_comment': None, 'salary': 12000.5, 'office': 401, 'depart_id': 1}, {'id': 3, 'name': 'xiaomeng', 'sex': 'male', 'age': 35, 'hire_date': datetime.date(2019, 6, 7), 'post': 'teacher', 'post_comment': None, 'salary': 15000.99, 'office': 401, 'depart_id': 1}, {'id': 4, 'name': 'xiaona', 'sex': 'female', 'age': 29, 'hire_date': datetime.date(2018, 9, 6), 'post': 'teacher', 'post_comment': None, 'salary': 11000.8, 'office': 401, 'depart_id': 1}, {'id': 5, 'name': 'xiaoqi', 'sex': 'female', 'age': 27, 'hire_date': datetime.date(2022, 8, 6), 'post': 'teacher', 'post_comment': None, 'salary': 13000.7, 'office': 401, 'depart_id': 1}, {'id': 6, 'name': 'suimeng', 'sex': 'male', 'age': 33, 'hire_date': datetime.date(2023, 3, 6), 'post': 'teacher', 'post_comment': None, 'salary': 14000.62, 'office': 401, 'depart_id': 1}, {'id': 7, 'name': '娜娜', 'sex': 'female', 'age': 69, 'hire_date': datetime.date(2010, 3, 7), 'post': 'sale', 'post_comment': None, 'salary': 300.13, 'office': 402, 'depart_id': 2}, {'id': 8, 'name': '芳芳', 'sex': 'male', 'age': 45, 'hire_date': datetime.date(2014, 5, 18), 'post': 'sale', 'post_comment': None, 'salary': 400.45, 'office': 402, 'depart_id': 2}, {'id': 9, 'name': '小明', 'sex': 'male', 'age': 34, 'hire_date': datetime.date(2016, 1, 3), 'post': 'sale', 'post_comment': None, 'salary': 350.8, 'office': 402, 'depart_id': 2}, {'id': 10, 'name': '亚洲', 'sex': 'female', 'age': 42, 'hire_date': datetime.date(2017, 2, 27), 'post': 'sale', 'post_comment': None, 'salary': 320.99, 'office': 402, 'depart_id': 2}]

(2)获取一行数据(fetchone)

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

sql = 'select * from emp limit 10'
cursor.execute(sql)
result = cursor.fetchone()  # 返回一行数据
print(result)
{'id': 1, 'name': 'dream', 'sex': 'male', 'age': 78, 'hire_date': datetime.date(2022, 3, 6), 'post': '陌夜痴梦久生情', 'post_comment': None, 'salary': 730.33, 'office': 401, 'depart_id': 1}

(3)获取查询指定结果量(fetchmany)

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

sql = 'select * from emp limit 10'
cursor.execute(sql)
result = cursor.fetchmany(2)  
# fetchmany(size) 获取查询结果集中指定数量的记录,size默认为1
print(result)
[{'id': 1, 'name': 'dream', 'sex': 'male', 'age': 78, 'hire_date': datetime.date(2022, 3, 6), 'post': '陌夜痴梦久生情', 'post_comment': None, 'salary': 730.33, 'office': 401, 'depart_id': 1}, {'id': 2, 'name': 'mengmeng', 'sex': 'female', 'age': 25, 'hire_date': datetime.date(2022, 1, 2), 'post': 'teacher', 'post_comment': None, 'salary': 12000.5, 'office': 401, 'depart_id': 1}]

(4)移动光标

cursor.scroll(1, 'relative')  # 相对于当前位置往后移动一个单位
cursor.scroll(1, 'absolute')  # 相对于起始位置往后移动一个单位

【5】插入操作

  • 插入操作中参数可以以元组、列表和字典形式传入
  • 需要使用到占位符 “%s”,注意这只是个占位符,不同于Python 中字符串格式化中的转换说明符。
转换说明符 解释
%d、%i 转换为带符号的十进制数
%o 转换为带符号的八进制数
%x、%X 转换为带符号的十六进制数
%e 转化为科学计数法表示的浮点数(e 小写)
%E 转化为科学计数法表示的浮点数(E 小写)
%f、%F 转化为十进制浮点数
%g 智能选择使用 %f 或 %e 格式
%G 智能选择使用 %F 或 %E 格式
%c 格式化字符及其ASCII码
%r 使用 repr() 函数将表达式转换为字符串
%s 使用 str() 函数将表达式转换为字符串
  • 未插入之前

image-20240120132345467

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

# 元组、列表形式传参
sql = 'insert into person(NAME,AGE,HEIGH,SEX) VALUES(%s, %s, %s,%s)'
cursor.execute(sql, ("dream", 28, 160, '女'))
conn.commit()

# # 字典形式传参
sql = 'insert into person(NAME,AGE,HEIGH,SEX) VALUES(%(person_name)s, %(person_age)s, %(person_height)s, %(person_sex)s)'
cursor.execute(sql, {"person_height": 190, "person_name": "opp", "person_age": 18, 'person_sex': "男"})
conn.commit()
  • 插入之后

image-20240120132439783

【6】更新操作

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)


sql = 'update  person set SEX=%s where NAME = %s'
cursor.execute(sql, ['保密', 'dream'])
conn.commit()

image-20240120132756584

【7】删除操作

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)


sql = 'delete from person where NAME = %s'
cursor.execute(sql, ['dream'])
conn.commit()
  • 增删改需要有提交事务的操作,除了execute方法,还有批量操作方法executemany()。

【8】批量插入操作

# 导入pymysql模块
import pymysql
from pymysql.cursors import DictCursor

#  连接数据库,创建连接对象
conn = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='1314521',
    port=3306,
    database='emp_data'
)
# 创建游标,查询数据以元组形式返回
# cursor = conn.cursor()

# 创建游标,查询数据以字典形式返回
cursor = conn.cursor(DictCursor)

sql = 'insert into person(NAME,AGE,HEIGH,SEX) VALUES(%s, %s, %s,%s)'
param = [("kenny", 28, 190, "男"), ("liu", 28, 180, "女")]  # 元组列表作为传入参数
cursor.executemany(sql, param)
conn.commit()

【9】总结模版

import pymysql
from pymysql.cursors import DictCursor


class MySQLHandler(object):
    def __init__(self, host='127.0.0.1', port=3306, user='root',
                 cursorclass=DictCursor, charset='utf8mb4', *,
                 password, database):
        #  连接数据库,创建连接对象
        self.conn = pymysql.connect(
            host=host,
            user=user,
            password=password,
            port=port,
            database=database,
            cursorclass=cursorclass,
            charset=charset
        )
        #  创建游标对象
        self.cursor = self.conn.cursor()

    # 提交事务装饰监控器
    @staticmethod
    def commit_sql(func):
        def inner(*args, **kwargs):
            self = args[0]
            try:
                res = func(*args, **kwargs)
                self.conn.commit()
                return res
            except Exception as e:
                self.conn.rollback()
                return e
            finally:
                self.close()

        return inner

    # 查询单条数据
    @commit_sql
    def search_one(self, sql):
        self.cursor.execute(sql)
        result = self.cursor.fetchone()
        return result

    # 查询所有数据
    @commit_sql
    def search_all(self, sql):
        self.cursor.execute(sql)
        results = self.cursor.fetchall()
        return results

    # 插入数据
    @commit_sql
    def insert_data(self, table_name, **kwargs):
        if kwargs:
            sql = f'INSERT INTO {table_name} ({", ".join(kwargs.keys())}) VALUES {tuple(kwargs.values())}'
        else:
            sql = f'INSERT INTO {table_name} () VALUES ()'
        print(f'当前SQL语句 :>>>> {sql}')
        return self.cursor.execute(sql)

    @commit_sql
    # 更新数据
    def update_data(self, sql):
        self.cursor.execute(sql)
        return sql

    # 关闭链接和服务对象
    def close(self):
        self.cursor.close()
        self.conn.close()

【四】SQL注入问题

【1】什么是SQL注入问题

  • SQL注入是一种常见的网络攻击手法
  • 它利用sql的语法特性和程序员编写程序时产生的漏洞,用一些特殊符号的组合产生特殊的含义,使得正常的sql语句失效,从而逃脱正常的业务逻辑,完成一些如跳过密码验证等的非法操作。

【2】产生原因

  • SQL语句使用了动态拼接的方式。

【3】示例

(0)准备

  • 创建表
create table user(
	id int primary key auto_increment,
    name varchar(32),
    password varchar(32)
);
  • 插入数据
insert into user(name,password) values("dream","521"),("chimeng","1314");
  • 查看数据
select * from user;
+----+---------+----------+
| id | name    | password |
+----+---------+----------+
|  1 | dream   | 521      |
|  2 | chimeng | 1314     |
+----+---------+----------+
2 rows in set (0.00 sec)

(1)登录注册

import pymysql


def connect_mysql():
    import pymysql

    # (1)链接数据库
    conn = pymysql.connect(
        # 指定 ip端口
        host='127.0.0.1',
        port=3306,

        # 指定用户名密码
        user='root',
        password='1314521',

        # 指定数据库
        database='emp_data',

        # 指定编码
        charset='utf8'
    )

    return conn


def create_cursor(username, password):
    conn = connect_mysql()
    # (2)创建游标对象 - 执行命令 对象
    # cursor=pymysql.cursors.DictCursor:将查询的参数以字典的形式返回
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

    # (3)创建SQL语句
    sql = 'select * from user where name = %s and password=%s;'%(username,password)

    rows = cursor.execute(sql)
    return rows, cursor


def register():
    username = input("username:>>>>").strip()
    password = input("password:>>>>").strip()
    rows, cursor = create_cursor(username, password)
    if rows:
        print("successfully login")
        print(cursor.fetchall())
    else:
        print("Failed to login")


if __name__ == '__main__':
    register()

image-20240120155825883

(2)问题

  • 上面的登录注册SQL语句为
'select * from user where name = "%s" and password= "%s";' % (username, password)
  • 没有对用户的输入做任何处理,直接放到了SQL语句中。
  • 那么,当黑客输入了dream OR 1=1 – dream 作为用户名时,原来的SQL语句就会变成下面的样子:
select * from user where name = dream or 1=1 -- dream and password="";
  • where name =dream or 1=1 是一个恒成立的条件,所以无论输入什么用户名都会返回True;
  • -- 后面的语句被当作注释忽略掉了,密码验证也被跳过。
  • 最终,绕过验证,成功登录。

【4】解决办法

  • 针对参数不要采用拼接处理,交给pymysql中的方法(execute)自动处理,并对输入数据进行检查校验
# 元组,列表形式
param = ('dream','123445')
sql = 'SELECT username FROM user WHERE name = %s AND password = %s'
cursor.execute(sql, param)

# 字典形式
param = {'name':'hope','password':'123445'}
sql = 'SELECT username FROM user WHERE name = %(name)s AND password = %(pwd)s'
cursor.execute(sql, param)
  • 需要注意的是,不要因为参数是其他类型而换掉 %s,pymysql 的占位符并不是 python 的通用格式化转换说明符。
  • 同时,也不要因为参数是 string 就在 %s 两边加引号,mysql 会自动去处理。

【五】PyMySQL进阶

【1】主动提交事务

  • 因为在增删改查中,只有查的权限是最简单的,因此查无需过多的权限
  • 但是增删改都涉及到数据库数据的变动,需要额外的确认才行(即提交事务)
import pymysql


def create_mysql_connection():
    conn = pymysql.connect(
        # 指定 IP 和 PORT
        host='127.0.0.1',
        port=3306,

        # 指定用户名和密码
        user='root',
        password='1314521',

        # 指定默认编码
        charset='utf8',

        # 指定数据库
        database='day05',
    )

    # 创建游标对象
    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # 返回游标对象
    return cursor, conn


cursor, conn = create_mysql_connection()


# 增加数据
def add_data():
    # (1) 拿到想要处理的数据
    username = input("Username:>>>").strip()
    password = input("Password:>>>").strip()

    # (2) 创建SQL语句
    sql = "INSERT INTO user(name,password) values (%s,%s);"

    # (3)查看SQL语句执行状态
    rows = cursor.execute(sql, (username, password))
    print(rows)

    # (4) 提交事务 :确认此次数据库更改操作
    conn.commit()


# 更改数据
def change_data():
    username = input("Username:>>>").strip()
    password = input("Password:>>>").strip()
    sql = "update user set name=%s, password=%s where id=1;"

    rows = cursor.execute(sql, (username, password))
    print(rows)

    conn.commit()


# 删除数据
def delete_data():
    sql = "delete from user where id =1;"

    rows = cursor.execute(sql)
    print(rows)

    conn.commit()


# 查询数据
def check_data():
    sql = "select * from user"
    cursor.execute(sql)
    print(cursor.fetchall())


if __name__ == '__main__':
    # add_data()
    # change_data()
    # delete_data()
    check_data()

【2】自动提交事务

import pymysql


def create_mysql_connection():
    conn = pymysql.connect(
        # 指定 IP 和 PORT
        host='127.0.0.1',
        port=3306,

        # 指定用户名和密码
        user='root',
        password='1314521',

        # 指定默认编码
        charset='utf8',

        # 指定数据库
        database='day05',

        # 自动提交事务
        autocommit=True
    )

    # 创建游标对象
    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # 返回游标对象
    return cursor, conn


cursor, conn = create_mysql_connection()


# 增加数据
def add_data():
    # (1) 拿到想要处理的数据
    username = input("Username:>>>").strip()
    password = input("Password:>>>").strip()

    # (2) 创建SQL语句
    sql = "INSERT INTO user(name,password) values (%s,%s);"

    # (3)查看SQL语句执行状态
    rows = cursor.execute(sql, (username, password))
    print(rows)




# 更改数据
def change_data():
    username = input("Username:>>>").strip()
    password = input("Password:>>>").strip()
    sql = "update user set name=%s, password=%s where id=1;"

    rows = cursor.execute(sql, (username, password))
    print(rows)



# 删除数据
def delete_data():
    sql = "delete from user where id =1;"

    rows = cursor.execute(sql)
    print(rows)




# 查询数据
def check_data():
    sql = "select * from user"
    cursor.execute(sql)
    print(cursor.fetchall())


if __name__ == '__main__':
    # add_data()
    # change_data()
    # delete_data()
    check_data()
posted @ 2024-01-29 21:44  Chimengmeng  阅读(29)  评论(0编辑  收藏  举报