Skip to main content

重构数据访问层

Copilot Chat 可以建议将数据访问代码与业务逻辑分离的方法,从而使应用程序更易于维护和缩放。

硬编码的 SQL 查询和紧密耦合的数据访问代码会使数据库缩放或切换变得困难,并且通常会导致逻辑重复。

Copilot Chat 建议实现仓库模式或数据访问层来提取数据库交互,使代码更加模块化并减少冗余。 它还有助于将 SQL 查询重构为优化版本,从而提高性能和可维护性。

Note

本文中所述的响应是示例。 Copilot Chat 响应是不确定的,所以你可能会得到与这里所显示的不同的响应。

示例方案

这段 Python 代码连接到 SQLite 数据库,检索用户记录,并返回用户数据。 但是,它没有提取数据库连接逻辑,并且使用了容易受 SQL 注入攻击的硬编码查询。

import sqlite3

def get_user_by_id(user_id):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(f"SELECT display_name FROM users WHERE id = {user_id}")
    user = cursor.fetchone()
    conn.close()
    return user

示例提示 1

可以首先向 Copilot 提出一个关于如何改进代码的一般性问题。

How can I improve this code to make it safe and easier to update and expand? List possible improvements but don't show revised code.

示例响应 1

Copilot 提出了一些改进代码的建议:

  • 提取数据库连接详细信息。
  • 使用参数化查询来防止 SQL 注入。
  • 使用上下文管理器来确保在发生错误时数据库连接能够正确关闭。
  • 添加错误处理。

示例提示 2

可以根据对第一个提示的响应来编写一个更具体的提示。

Rewrite this code to make it more scalable and easier to maintain. Use a context manager. Avoid hardcoded SQL queries and tightly coupled data access code. Instead, use a repository pattern to abstract database interactions and make the code more modular and reusable. Where possible optimize the code to improve performance. Include error trapping, and make sure the code is not vulnerable to SQL injection.

示例响应 2

Copilot 建议将代码拆分为多个文件,每个文件都有特定的用途。 它还提供用于返回用户详细信息的示例代码。

建议的更改实现了仓库模式,并使用参数化查询来避免 SQL 注入。

# database.py
import sqlite3

class Database:
    def __init__(self, db_path):
        self.db_path = db_path

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_path)
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type or exc_val or exc_tb:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.conn.close()

# user_repository.py
class UserRepository:
    def __init__(self, db):
        self.db = db

    def get_user_by_id(self, user_id):
        try:
            self.db.cursor.execute("SELECT display_name FROM users WHERE id = ?", (user_id,))
            user = self.db.cursor.fetchone()
            return user
        except sqlite3.Error as e:
            print(f"An error occurred: {e}")
            return None

# main.py
from database import Database
from user_repository import UserRepository

def get_user_by_id(user_id):
    with Database('database.db') as db:
        user_repo = UserRepository(db)
        return user_repo.get_user_by_id(user_id)

# Example usage
if __name__ == "__main__":
    user = get_user_by_id(1)
    if user:
        print(user)
    else:
        print("User not found or an error occurred.")

修改后的代码在以下几个方面对原代码进行了改进:

  • 代码现在实现基本数据访问层。
  • Database 类处理与 SQLite 数据库的连接,使用 __enter____exit__ 方法实现上下文管理器协议。 这可确保正确管理数据库连接,包括提交事务和关闭连接。
  • UserRepository 类封装用于访问用户数据的逻辑。
  • 查询的值被参数化以防止 SQL 注入。
  • 捕获错误,并将详细信息打印到控制台。

其他阅读材料