全面解析Node.js中MySQL数据模型类的声明方式

文章目录

Node.js 中 MySQL 数据模型实现指南:从自定义封装到主流 ORM 框架应用

在 Node.js 开发中,MySQL 数据库交互是常见需求。数据模型类作为应用程序与数据库之间的桥梁,其设计直接影响代码质量和开发效率。本文将从基础的自定义封装开始,逐步深入到 Sequelize 和 TypeORM 等主流 ORM 框架的应用,全面解析 Node.js 中 MySQL 数据模型类的声明方式。

一、自定义 MySQL 数据模型封装

对于简单项目,我们可以手动封装数据模型类,直接使用 mysql2 库进行数据库操作。

1. 基础数据库连接类

首先创建一个数据库连接基础类,封装连接逻辑:

const mysql = require('mysql2/promise');

class BaseModel {
  constructor() {
    // 数据库配置
    this.config = {
      host: process.env.DB_HOST || 'localhost',
      user: process.env.DB_USER || 'root',
      password: process.env.DB_PASSWORD || '',
      database: process.env.DB_NAME || 'test',
      waitForConnections: true,
      connectionLimit: 10,
      queueLimit: 0
    };
    
    // 连接池
    this.pool = mysql.createPool(this.config);
  }
  
  /**
   * 获取数据库连接
   * @returns {Promise} 数据库连接对象
   */
  async getConnection() {
    try {
      return await this.pool.getConnection();
    } catch (error) {
      console.error('获取数据库连接失败:', error);
      throw error;
    }
  }
  
  /**
   * 释放数据库连接
   * @param {Object} connection - 数据库连接对象
   */
  releaseConnection(connection) {
    if (connection) {
      connection.release();
    }
  }
}

module.exports = BaseModel;

2. 具体数据模型类实现

基于基础连接类,实现具体的数据模型,例如用户模型:

const BaseModel = require('../db/BaseModel');

class User extends BaseModel {
  constructor() {
    super();
    this.table = 'users';
  }
  
  /**
   * 根据ID查询用户
   * @param {number} id - 用户ID
   * @returns {Promise<Object>} 用户信息
   */
  async findById(id) {
    let connection = null;
    try {
      connection = await this.getConnection();
      const [rows] = await connection.execute(
        `SELECT * FROM ${this.table} WHERE id = ?`,
        [id]
      );
      return rows[0] || null;
    } catch (error) {
      console.error(`查询ID为${id}的用户失败:`, error);
      throw error;
    } finally {
      this.releaseConnection(connection);
    }
  }
  
  /**
   * 查询所有用户
   * @param {Object} options - 查询选项
   * @param {number} options.limit - 限制数量
   * @param {number} options.offset - 偏移量
   * @returns {Promise<Array>} 用户列表
   */
  async findAll(options = {}) {
    const { limit = 10, offset = 0 } = options;
    let connection = null;
    try {
      connection = await this.getConnection();
      const [rows] = await connection.execute(
        `SELECT * FROM ${this.table} LIMIT ? OFFSET ?`,
        [limit, offset]
      );
      return rows;
    } catch (error) {
      console.error('查询所有用户失败:', error);
      throw error;
    } finally {
      this.releaseConnection(connection);
    }
  }
  
  /**
   * 创建新用户
   * @param {Object} userData - 用户数据
   * @returns {Promise<Object>} 新增用户信息
   */
  async create(userData) {
    const { username, email, password, age } = userData;
    let connection = null;
    try {
      connection = await this.getConnection();
      const [result] = await connection.execute(
        `INSERT INTO ${this.table} (username, email, password, age, created_at, updated_at) 
         VALUES (?, ?, ?, ?, NOW(), NOW())`,
        [username, email, password, age]
      );
      
      // 返回新创建的用户
      return this.findById(result.insertId);
    } catch (error) {
      console.error('创建用户失败:', error);
      throw error;
    } finally {
      this.releaseConnection(connection);
    }
  }
  
  /**
   * 更新用户信息
   * @param {number} id - 用户ID
   * @param {Object} userData - 要更新的用户数据
   * @returns {Promise<boolean>} 更新是否成功
   */
  async update(id, userData) {
    // 构建更新字段和参数
    const fields = [];
    const params = [];
    
    Object.entries(userData).forEach(([key, value]) => {
      if (key !== 'id') { // 不允许更新ID
        fields.push(`${key} = ?`);
        params.push(value);
      }
    });
    
    // 添加更新时间和ID参数
    fields.push('updated_at = NOW()');
    params.push(id);
    
    let connection = null;
    try {
      connection = await this.getConnection();
      const [result] = await connection.execute(
        `UPDATE ${this.table} SET ${fields.join(', ')} WHERE id = ?`,
        params
      );
      
      return result.affectedRows > 0;
    } catch (error) {
      console.error(`更新ID为${id}的用户失败:`, error);
      throw error;
    } finally {
      this.releaseConnection(connection);
    }
  }
  
  /**
   * 删除用户
   * @param {number} id - 用户ID
   * @returns {Promise<boolean>} 删除是否成功
   */
  async delete(id) {
    let connection = null;
    try {
      connection = await this.getConnection();
      const [result] = await connection.execute(
        `DELETE FROM ${this.table} WHERE id = ?`,
        [id]
      );
      
      return result.affectedRows > 0;
    } catch (error) {
      console.error(`删除ID为${id}的用户失败:`, error);
      throw error;
    } finally {
      this.releaseConnection(connection);
    }
  }
}

module.exports = new User();

3. 使用自定义数据模型

使用上述用户模型进行数据库操作:

// 使用用户模型
const userModel = require('./models/User');

async function userOperations() {
  try {
    // 创建用户
    const newUser = await userModel.create({
      username: 'john_doe',
      email: 'john@example.com',
      password: 'hashed_password',
      age: 30
    });
    console.log('创建的新用户:', newUser);
    
    // 查询用户
    const user = await userModel.findById(newUser.id);
    console.log('查询到的用户:', user);
    
    // 更新用户
    const updateSuccess = await userModel.update(newUser.id, {
      age: 31,
      email: 'john.updated@example.com'
    });
    console.log('更新成功:', updateSuccess);
    
    // 查询所有用户
    const users = await userModel.findAll({ limit: 5 });
    console.log('用户列表:', users);
    
    // 删除用户
    const deleteSuccess = await userModel.delete(newUser.id);
    console.log('删除成功:', deleteSuccess);
  } catch (error) {
    console.error('操作失败:', error);
  }
}

userOperations();

二、使用 ORM 框架:Sequelize

对于复杂项目,使用 ORM(对象关系映射)框架可以大大提高开发效率。Sequelize 是 Node.js 中最流行的 ORM 框架之一。

1. Sequelize 配置与初始化

const { Sequelize } = require('sequelize');

// 初始化 Sequelize 实例
const sequelize = new Sequelize(
  process.env.DB_NAME || 'test',
  process.env.DB_USER || 'root',
  process.env.DB_PASSWORD || '',
  {
    host: process.env.DB_HOST || 'localhost',
    dialect: 'mysql',
    pool: {
      max: 10,
      min: 0,
      acquire: 30000,
      idle: 10000
    },
    logging: process.env.NODE_ENV === 'development' ? console.log : false
  }
);

// 测试数据库连接
async function testConnection() {
  try {
    await sequelize.authenticate();
    console.log('数据库连接已成功建立。');
  } catch (error) {
    console.error('无法连接到数据库:', error);
  }
}

testConnection();

module.exports = sequelize;

2. 使用 Sequelize 定义数据模型

const { DataTypes, Model } = require('sequelize');
const sequelize = require('../db/sequelize');
const bcrypt = require('bcrypt');

class User extends Model {
  // 实例方法:验证密码
  async isValidPassword(password) {
    return await bcrypt.compare(password, this.password);
  }
  
  // 实例方法:转换为JSON时隐藏敏感信息
  toJSON() {
    const values = { ...this.get() };
    delete values.password;
    return values;
  }
}

User.init({
  // 定义模型属性
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  username: {
    type: DataTypes.STRING(50),
    allowNull: false,
    unique: true,
    validate: {
      notEmpty: true,
      len: [3, 50]
    }
  },
  email: {
    type: DataTypes.STRING(100),
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    }
  },
  password: {
    type: DataTypes.STRING(100),
    allowNull: false,
    validate: {
      notEmpty: true,
      len: [6, 100]
    }
  },
  age: {
    type: DataTypes.INTEGER,
    allowNull: true,
    validate: {
      min: 0,
      max: 150
    }
  },
  createdAt: {
    type: DataTypes.DATE,
    defaultValue: DataTypes.NOW
  },
  updatedAt: {
    type: DataTypes.DATE,
    defaultValue: DataTypes.NOW
  }
}, {
  // 其他模型选项
  sequelize, // 我们需要传递连接实例
  modelName: 'User', // 模型名称
  tableName: 'users', // 表名
  timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
  underscored: true, // 使用下划线命名法
  hooks: {
    // 密码加密钩子
    beforeCreate: async (user) => {
      if (user.password) {
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
      }
    },
    beforeUpdate: async (user) => {
      if (user.changed('password')) {
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
      }
    }
  }
});

module.exports = User;

3. Sequelize 模型关联

Sequelize 强大之处在于处理表之间的关系,例如用户和文章的一对多关系:

const { DataTypes, Model } = require('sequelize');
const sequelize = require('../db/sequelize');
const User = require('./SequelizeUser');

class Article extends Model {}

Article.init({
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  title: {
    type: DataTypes.STRING(200),
    allowNull: false,
    validate: {
      notEmpty: true
    }
  },
  content: {
    type: DataTypes.TEXT,
    allowNull: false
  },
  userId: {
    type: DataTypes.INTEGER,
    allowNull: false,
    references: {
      model: User,
      key: 'id'
    },
    field: 'user_id'
  }
}, {
  sequelize,
  modelName: 'Article',
  tableName: 'articles',
  timestamps: true,
  underscored: true
});

// 建立关联:一个用户可以有多个文章
User.hasMany(Article, { 
  foreignKey: 'userId',
  as: 'articles' 
});

// 建立关联:一篇文章属于一个用户
Article.belongsTo(User, { 
  foreignKey: 'userId',
  as: 'author' 
});

module.exports = Article;

4. 使用 Sequelize 模型进行操作

const User = require('./models/SequelizeUser');
const Article = require('./models/Article');

async function ormOperations() {
  try {
    // 创建用户
    const user = await User.create({
      username: 'jane_smith',
      email: 'jane@example.com',
      password: 'secure_password',
      age: 28
    });
    console.log('创建的用户:', user.toJSON());
    
    // 创建文章
    const article = await Article.create({
      title: 'Sequelize 教程',
      content: '这是一篇关于 Sequelize ORM 的教程文章...',
      userId: user.id
    });
    console.log('创建的文章:', article.toJSON());
    
    // 查询用户及其文章
    const userWithArticles = await User.findByPk(user.id, {
      include: [{ model: Article, as: 'articles' }]
    });
    console.log('用户及其文章:', userWithArticles.toJSON());
    
    // 查询所有用户
    const users = await User.findAll({
      limit: 10,
      order: [['createdAt', 'DESC']]
    });
    console.log('用户列表:', users.map(u => u.toJSON()));
    
    // 更新用户
    user.age = 29;
    await user.save();
    console.log('更新后的用户年龄:', user.age);
    
    // 删除文章
    await article.destroy();
    console.log('文章已删除');
    
  } catch (error) {
    console.error('ORM 操作失败:', error);
  }
}

ormOperations();

三、使用 ORM 框架:TypeORM

TypeORM 是另一个流行的 ORM 框架,特别适合与 TypeScript 一起使用,提供了类似 JPA/Hibernate 的体验。

1. TypeORM 配置与初始化

module.exports = {
  type: 'mysql',
  host: process.env.DB_HOST || 'localhost',
  port: 3306,
  username: process.env.DB_USER || 'root',
  password: process.env.DB_PASSWORD || '',
  database: process.env.DB_NAME || 'test',
  synchronize: process.env.NODE_ENV === 'development', // 开发环境自动同步数据库结构
  logging: process.env.NODE_ENV === 'development',
  entities: [
    'src/entities/**/*.ts',
    'dist/entities/**/*.js'
  ],
  migrations: [
    'src/migrations/**/*.ts',
    'dist/migrations/**/*.js'
  ],
  subscribers: [
    'src/subscribers/**/*.ts',
    'dist/subscribers/**/*.js'
  ],
  cli: {
    entitiesDir: 'src/entities',
    migrationsDir: 'src/migrations',
    subscribersDir: 'src/subscribers'
  },
  poolSize: 10
};

初始化数据库连接:

const { createConnection } = require('typeorm');
const config = require('../ormconfig');

let connection;

async function getConnection() {
  if (!connection) {
    try {
      connection = await createConnection(config);
      console.log('数据库连接已成功建立');
    } catch (error) {
      console.error('数据库连接失败:', error);
      throw error;
    }
  }
  return connection;
}

module.exports = { getConnection };

2. 使用 TypeORM 定义实体

const { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, BeforeInsert, BeforeUpdate } = require('typeorm');
const bcrypt = require('bcrypt');
const Article = require('./Article');

@Entity({ name: 'users' })
class User {
  @PrimaryGeneratedColumn()
  id;

  @Column({
    type: 'varchar',
    length: 50,
    unique: true,
    nullable: false
  })
  username;

  @Column({
    type: 'varchar',
    length: 100,
    unique: true,
    nullable: false
  })
  email;

  @Column({
    type: 'varchar',
    length: 100,
    nullable: false
  })
  password;

  @Column({
    type: 'int',
    nullable: true
  })
  age;

  @CreateDateColumn({ name: 'created_at' })
  createdAt;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt;

  @OneToMany(() => Article, article => article.author)
  articles;

  @BeforeInsert()
  @BeforeUpdate()
  async hashPassword() {
    if (this.password) {
      const salt = await bcrypt.genSalt(10);
      this.password = await bcrypt.hash(this.password, salt);
    }
  }

  async isValidPassword(password) {
    return await bcrypt.compare(password, this.password);
  }

  toJSON() {
    const { password, ...user } = this;
    return user;
  }
}

module.exports = User;

3. TypeORM 实体关联

const { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } = require('typeorm');
const User = require('./User');

@Entity({ name: 'articles' })
class Article {
  @PrimaryGeneratedColumn()
  id;

  @Column({
    type: 'varchar',
    length: 200,
    nullable: false
  })
  title;

  @Column({
    type: 'text',
    nullable: false
  })
  content;

  @Column({ name: 'user_id' })
  userId;

  @ManyToOne(() => User, user => user.articles)
  @JoinColumn({ name: 'user_id' })
  author;

  @CreateDateColumn({ name: 'created_at' })
  createdAt;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt;
}

module.exports = Article;

4. 使用 TypeORM 进行数据库操作

const { getConnection } = require('./db/connection');
const User = require('./entities/User');
const Article = require('./entities/Article');

async function typeormOperations() {
  try {
    const connection = await getConnection();
    const userRepository = connection.getRepository(User);
    const articleRepository = connection.getRepository(Article);

    // 创建用户
    const user = new User();
    user.username = 'bob_williams';
    user.email = 'bob@example.com';
    user.password = 'typeorm_password';
    user.age = 35;
    const savedUser = await userRepository.save(user);
    console.log('创建的用户:', savedUser.toJSON());

    // 创建文章
    const article = new Article();
    article.title = 'TypeORM 教程';
    article.content = '这是一篇关于 TypeORM 的教程文章...';
    article.author = savedUser;
    const savedArticle = await articleRepository.save(article);
    console.log('创建的文章:', savedArticle);

    // 查询用户及其文章
    const userWithArticles = await userRepository.findOne({
      where: { id: savedUser.id },
      relations: ['articles']
    });
    console.log('用户及其文章:', userWithArticles.toJSON());

    // 查询所有用户
    const users = await userRepository.find({
      take: 10,
      order: { createdAt: 'DESC' }
    });
    console.log('用户列表:', users.map(u => u.toJSON()));

    // 更新用户
    savedUser.age = 36;
    await userRepository.save(savedUser);
    console.log('更新后的用户年龄:', savedUser.age);

    // 删除文章
    await articleRepository.remove(savedArticle);
    console.log('文章已删除');

  } catch (error) {
    console.error('TypeORM 操作失败:', error);
  }
}

typeormOperations();

四、自定义封装 vs Sequelize vs TypeORM 特性功能对比

特性自定义封装SequelizeTypeORM
学习曲线中等中高
代码量多(需手动实现所有功能)
灵活性极高(完全可控)中等(框架约束)中等(框架约束)
功能丰富度基础功能(需自行扩展)丰富丰富
TypeScript 支持需手动定义类型良好(有类型定义)原生支持(最佳体验)
关联处理需手动编写 JOIN 语句内置强大的关联系统内置强大的关联系统,类似 JPA
迁移工具需自行实现内置迁移系统内置迁移系统
查询能力原生 SQL,灵活但繁琐强大的查询构建器 + 原始 SQL强大的查询构建器 + 原始 SQL
钩子/生命周期需自行实现丰富的钩子系统丰富的装饰器钩子系统
自动验证需自行实现内置验证系统内置验证系统
社区活跃度无(自建)非常活跃活跃
文档完善度无(自建)完善完善
适用场景小型项目、简单查询、特殊需求中大型项目、快速开发TypeScript 项目、企业级应用
性能高(可针对性优化)中(略低于原生)中(略低于原生)
维护成本高(需自行维护所有功能)低(框架维护)低(框架维护)

五、注意事项

  1. 安全性考虑

    • 始终使用参数化查询或 ORM 框架防止 SQL 注入攻击
    • 敏感数据(如密码)必须加密存储,绝不能明文保存
    • 实现适当的输入验证,无论是自定义封装还是使用 ORM
    • 遵循最小权限原则配置数据库用户权限
  2. 连接管理

    • 使用连接池管理数据库连接,避免频繁创建和关闭连接
    • 合理设置连接池大小,根据应用负载调整
    • 确保所有连接在操作完成后正确释放,防止连接泄漏
  3. 事务处理

    • 对于多步数据库操作,使用事务确保数据一致性
    • 了解并正确实现各 ORM 框架的事务机制
    • 确保事务在发生错误时能够正确回滚
  4. 性能优化

    • 合理使用索引提升查询性能
    • 避免 SELECT *,只查询需要的字段
    • 使用适当的分页机制处理大量数据
    • 了解 ORM 框架的查询缓存机制并合理利用
  5. 迁移与版本控制

    • 使用 ORM 提供的迁移工具管理数据库结构变更
    • 为所有数据库变更建立版本控制,便于团队协作和环境部署
    • 测试环境与生产环境的数据库迁移流程保持一致
  6. 日志与监控

    • 记录数据库操作日志,特别是错误日志
    • 监控慢查询,及时优化
    • 生产环境中避免打印敏感信息到日志
  7. 环境配置

    • 不同环境(开发、测试、生产)使用不同的数据库配置
    • 通过环境变量管理配置,避免硬编码敏感信息
    • 开发环境禁用自动同步数据库结构到生产环境

六、总结

本文详细介绍了 Node.js 中 MySQL 数据模型的三种实现方式:自定义封装、Sequelize 和 TypeORM,每种方式都有其适用场景:

  1. 自定义封装:适合小型项目或对数据库操作有特殊需求的场景。优点是灵活性高、性能好、学习成本低;缺点是需要手动实现大量功能,代码量大,维护成本高。

  2. Sequelize:作为最成熟的 Node.js ORM 之一,适合大多数中大型项目。提供了丰富的功能和良好的社区支持,API 设计直观,同时支持 JavaScript 和 TypeScript。

  3. TypeORM:特别适合使用 TypeScript 的项目,提供了类似 JPA 的编程体验,对实体关系的处理非常强大,迁移系统完善,适合需要复杂数据模型的企业级应用。

选择合适的方案应考虑项目规模、团队熟悉度、性能需求和长期维护成本。小型项目可以从自定义封装开始,随着项目增长再迁移到 ORM 框架;中大型项目建议直接采用成熟的 ORM 框架,以提高开发效率和代码可维护性。

无论选择哪种方式,核心原则是确保代码的可读性、可维护性和安全性,同时兼顾性能需求。良好的数据模型设计是构建稳定、高效 Node.js 应用的基础。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值