《基于 Koa 的登录页面实战:封装数据库》

260 阅读6分钟

《基于 Koa 的登录页面实战:封装数据库》

一个完整的项目少不了数据库,更少不了对数据的增删改查,为了减少消耗性能,将数据进行封装。

image.png

server/config/index.js

const config = {
  database: {
    DATABASE: "notebook",
    USERNAME: "root",
    PASSWORD: "root",
    HOST: "localhost",
    PORT: "3306",
  },
};

module.exports = config;

  • DATABASE 字段指定了数据库的名称为 notebook
  • USERNAME 字段指定了数据库的用户名是 root
  • PASSWORD 字段指定了密码为 root
  • HOST 字段指定了数据库所在的主机地址为 localhost,这通常表示本地主机。
  • PORT 字段指定了数据库服务所监听的端口为 3306,这是 MySQL 数据库的常见默认端口。

并模块化导出,以供其它地方使用。

server/controllers/index.js

// 打造一个可以连接数据库的方法
const mysql = require("mysql2/promise.js");
const { database } = require("../config/index.js");

// 创建连接池
const pool = mysql.createPool({
  host: database.HOST,
  user: database.USERNAME,
  password: database.PASSWORD,
  database: database.DATABASE,
  port: database.PORT,
});

// 可以连接数据库的方法
const allServices = {
  async query(sql) {
    try {
      //通过连接池来创建连接
      const conn = await pool.getConnection();
      //对该连接执行某些操作
      const [rows, err] = await conn.query(sql);
      //释放连接
      pool.releaseConnection(conn);
      return Promise.resolve(rows);
    } catch (e) {
      return Promise.resolve(e);
    }
  },
};
const userFind = (username) => {
  const _sql = `SELECT * FROM users WHERE username="${username}"`;
  return allServices.query(_sql);
};
//登录
const userLogin = (username, password) => {
  const _sql = `SELECT * FROM users WHERE username="${username}" and password="${password}"`;
  return allServices.query(_sql);
};

const userRegister = (username, password, nickname) => {
  const _sql = `INSERT INTO users (username, password, nickname) VALUES ("${username}", "${password}", "${nickname}")`;
  return allServices.query(_sql);
};

// 根据分类查找数据
const findNoteListByType = (note_type, id) => {
  const _sql = `select * from note where note_type="${note_type}" and userId="${id}"`;
  return allServices.query(_sql);
};

const findBody = (id) => {
  const _sql = `select * from note where id="${id}"`;
  return allServices.query(_sql);
};

const notePublish = (values) => {
  const _sql = `insert into note (
      title, 
      note_type, 
      note_content, 
      head_img, 
      c_time, 
      m_time, 
      userId, 
      nickname) values("${values.title}", "${values.note_type}", "${values.note_content}", "${values.head_img}", "${values.c_time}", "${values.m_time}", "${values.userId}", "${values.nickname}");`;

  return allServices.query(_sql);
};

module.exports = {
  userLogin,
  userFind,
  userRegister,
  findNoteListByType,
  notePublish,
  findBody,
};

const mysql = require("mysql2/promise.js");
const { database } = require("../config/index.js");
  • 这两行分别引入了 mysql2/promise.js 模块和从 ../config/index.js 模块中解构出 database 对象。
// 创建连接池
const pool = mysql.createPool({
  host: database.HOST,
  user: database.USERNAME,
  password: database.PASSWORD,
  database: database.DATABASE,
  port: database.PORT,
});
  • 创建了一个 mysql 连接池 pool ,使用了从 database 对象中获取的数据库连接配置信息,包括主机地址、用户名、密码、数据库名称和端口号。

连接池的工作原理主要包含以下几个关键步骤:

  1. 初始化

    • 在应用程序启动时,连接池会创建一定数量的数据库连接,并将这些连接放入池中备用。
  2. 请求连接

    • 当应用程序需要进行数据库操作时,它向连接池请求一个连接。
  3. 分配连接

    • 连接池会从可用的连接中选择一个,并将其分配给请求的应用程序。如果池中没有可用连接,且未达到最大连接数限制,连接池会创建新的连接并分配。
  4. 使用连接

    • 应用程序使用分配到的连接进行数据库操作。
  5. 释放连接

    • 应用程序完成数据库操作后,将连接释放回连接池,而不是直接关闭连接。
  6. 连接维护

    • 连接池会定期检查池中的连接是否有效。如果发现无效连接(例如由于数据库服务器端关闭了连接),会将其从池中移除,并创建新的连接补充。
  7. 连接数量管理

    • 连接池会根据配置的最大连接数和最小连接数来动态调整连接的数量。如果当前连接数小于最小连接数,会创建新连接;如果连接数超过最大连接数,新的连接请求会等待直到有可用连接。

例如,假设连接池初始创建了 5 个连接,最大连接数设置为 10 。当有 6 个并发请求同时需要连接时,由于池内只有 5 个可用连接,连接池会创建第 6 个连接来满足需求。当这 6 个请求中的一些完成操作并释放连接后,这些连接会回到池中等待下次被分配使用。同时,连接池会按照一定的规则(如每隔一段时间)检查这些连接的有效性,确保后续使用时不会出现问题。

连接池的优势包括:

  1. 性能提升

    • 减少了创建和关闭数据库连接的时间开销,从而提高了应用程序的响应速度。
    • 特别在高并发场景下,能够快速满足大量连接请求,避免因频繁创建连接导致的性能瓶颈。
  2. 资源有效利用

    • 复用连接,避免了不必要的资源浪费,提高了系统资源的利用率。
  3. 稳定性增强

    • 控制连接数量,防止过多的连接对数据库造成过大压力,保障了数据库的稳定运行。
  4. 简化编程模型

    • 开发人员无需关心连接的创建和释放细节,专注于业务逻辑的实现。
  5. 提高系统的可扩展性

    • 可以根据系统负载动态调整连接池的参数,如连接数量等,以适应不同的业务需求。

然而,连接池也存在一些潜在的劣势:

  1. 配置复杂性

    • 需要合理地配置连接池的参数,如初始连接数、最大连接数、连接超时时间等,如果配置不当可能会影响性能。
  2. 内存占用

    • 维护连接池需要占用一定的内存空间,特别是当连接数量较大时。
  3. 潜在的连接泄漏

    • 如果应用程序在使用完连接后没有正确释放,可能导致连接泄漏,最终耗尽连接池资源。
  4. 故障传播

    • 连接池中的某个连接出现问题,可能会影响到使用该连接池的其他操作,增加了故障排查的复杂性。

例如,在一个电商网站的订单处理系统中,使用连接池可以快速处理大量并发的订单创建和查询请求,提高系统性能和稳定性。但如果连接池的最大连接数设置过低,在促销活动期间可能会导致请求排队等待连接,影响用户体验。

// 可以连接数据库的方法
const allServices = {
  async query(sql) {
    try {
      //通过连接池来创建连接
      const conn = await pool.getConnection();
      //对该连接执行某些操作
      const [rows, err] = await conn.query(sql);
      //释放连接
      pool.releaseConnection(conn);
      return Promise.resolve(rows);
    } catch (e) {
      return Promise.resolve(e);
    }
  },
};
  • 定义了一个名为 allServices 的对象,其中包含一个异步的 query 方法。该方法接受一个 sql 字符串作为参数。在方法内部,尝试通过连接池获取连接,执行传入的 sql 语句,并处理结果或捕获可能的错误。成功时返回解析后的结果行,出错时返回错误对象。

后面的增删改查都是封装的mysql执行方法。

将这些方法,模块化导出,以供其它模块使用,使得代码简介,复用性更好。