阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 数据库连接与ORM集成

数据库连接与ORM集成

作者:陈川 阅读数:14830人阅读 分类: Node.js

数据库连接的基本方式

Express应用中连接数据库通常有两种主流方式:直接使用数据库驱动或通过ORM工具。原生驱动连接更接近底层,适合需要精细控制查询的场景。以MySQL为例,使用mysql2包的连接代码如下:

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

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  database: 'test',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

async function queryUser(id) {
  const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
  return rows[0];
}

连接池配置参数中,connectionLimit控制最大连接数,queueLimit设定等待队列长度。这种方式的优势是执行原生SQL语句,但需要手动处理结果集转换。

ORM的核心价值

对象关系映射(ORM)解决了数据库表与程序对象的阻抗失配问题。以Sequelize为例,模型定义不仅包含字段映射,还能声明关联关系:

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

const User = sequelize.define('User', {
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  age: {
    type: DataTypes.INTEGER,
    validate: { min: 18 }
  }
}, {
  indexes: [{ fields: ['username'] }]
});

const Post = sequelize.define('Post', {
  title: DataTypes.STRING,
  content: DataTypes.TEXT
});

User.hasMany(Post);  // 一对多关联
Post.belongsTo(User); // 反向关联

模型验证规则如allowNullvalidate在数据持久化前自动生效。关联方法如hasMany会生成外键约束,查询时可使用include实现自动联表。

连接池的深度配置

生产环境需要优化连接池参数。以MongoDB的mongoose为例:

const mongoose = require('mongoose');
const options = {
  poolSize: 5, // 连接池大小
  connectTimeoutMS: 30000, // 连接超时
  socketTimeoutMS: 45000, // 套接字超时
  serverSelectionTimeoutMS: 5000 // 服务器选择超时
};

mongoose.connect('mongodb://localhost:27017/mydb', options)
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('Connection error:', err));

// 监听连接事件
mongoose.connection.on('connected', () => {
  console.log('Mongoose default connection open');
});

关键参数poolSize应根据应用并发量调整,过大会导致数据库负载过高。事件监听可以处理连接中断等异常情况。

事务处理的实现模式

ORM通常提供三种事务处理方式。TypeORM的显式事务示例:

import { getManager } from 'typeorm';

await getManager().transaction(async transactionalEntityManager => {
  await transactionalEntityManager.save(User, { name: 'Alice' });
  await transactionalEntityManager.update(Profile, { userId: 1 }, { age: 30 });
  
  // 如果此处抛出错误,整个事务回滚
  const user = await transactionalEntityManager.findOne(User, 1);
  user.credits += 100;
  await transactionalEntityManager.save(user);
});

装饰器事务通过注解简化代码:

@Transaction()
async transfer(@TransactionManager() manager: EntityManager) {
  await manager.decrement(Account, { id: 1 }, 'balance', 100);
  await manager.increment(Account, { id: 2 }, 'balance', 100);
}

性能优化策略

N+1查询是常见性能陷阱。Prisma的解决方案:

// 错误方式:产生N+1查询
const posts = await prisma.post.findMany();
const authors = await Promise.all(
  posts.map(post => prisma.user.findUnique({ where: { id: post.authorId } }))
);

// 正确方式:预加载关联数据
const postsWithAuthors = await prisma.post.findMany({
  include: { author: true }
});

批量操作能显著提升性能:

// 单条插入
for (const user of users) {
  await prisma.user.create({ data: user });
}

// 批量插入
await prisma.user.createMany({ data: users });

多数据库支持方案

某些ORM支持多数据源连接。MikroORM的配置示例:

const orm = await MikroORM.init({
  entities: [User, Post],
  dbName: 'main_db',
  type: 'postgresql',
  // 第二个数据源
  replicas: [{
    name: 'read-replica',
    host: 'replica.db.example.com',
    user: 'readonly_user'
  }]
});

// 使用主库写入
const em = orm.em.fork();
em.persist(new User(...));

// 使用从库查询
const readEm = orm.em.fork({ useReplica: true });
const users = await readEm.find(User, {});

读写分离配置中,所有写操作自动路由到主库,查询可分散到多个从库。

原生查询与ORM混合使用

复杂查询可能需要混合使用ORM和原生SQL。Knex.js与Objection.js组合示例:

const knex = require('knex')({
  client: 'pg',
  connection: process.env.DATABASE_URL
});

class Person extends Model {
  static get tableName() { return 'persons'; }

  static async findHighValueCustomers() {
    const sql = `
      SELECT p.*, SUM(o.total) as lifetime_value
      FROM persons p
      JOIN orders o ON o.customer_id = p.id
      GROUP BY p.id
      HAVING SUM(o.total) > ?
    `;
    return await knex.raw(sql, [10000]).then(res => res.rows);
  }
}

存储过程调用示例:

const result = await knex.raw(
  'CARDINALITY(?)', 
  ['{1,2,3}']
).then(res => res.rows[0].cardinality);

数据迁移管理

迁移文件应保持幂等性。Sequelize迁移示例:

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Products', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      name: {
        type: Sequelize.STRING(100),
        allowNull: false,
        unique: true
      },
      price: {
        type: Sequelize.DECIMAL(10,2),
        validate: { min: 0 }
      },
      createdAt: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.NOW
      }
    });

    await queryInterface.addIndex('Products', ['name'], {
      name: 'IDX_PRODUCT_NAME'
    });
  },

  down: async (queryInterface) => {
    await queryInterface.dropTable('Products');
  }
};

回滚操作应完全撤销up中的变更。字段修改需要单独迁移:

await queryInterface.changeColumn('Products', 'price', {
  type: Sequelize.DECIMAL(12,2),
  comment: '支持更高金额'
});

连接健康检查

需要实现主动健康监测机制。NestJS中的TypeORM健康检查:

import { HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator
  ) {}

  @Get()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database', { timeout: 300 })
    ]);
  }
}

自定义检查策略示例:

const checkConnection = async () => {
  try {
    const conn = await pool.getConnection();
    await conn.ping();
    conn.release();
    return true;
  } catch (err) {
    return false;
  }
};

setInterval(async () => {
  if (!await checkConnection()) {
    alertAdmin('DB connection lost');
  }
}, 60000);

连接失败的重试策略

指数退避算法实现:

const retryConnect = async (fn, retries = 3, delay = 1000) => {
  try {
    return await fn();
  } catch (err) {
    if (retries <= 0) throw err;
    await new Promise(res => setTimeout(res, delay));
    return retryConnect(fn, retries - 1, delay * 2);
  }
};

await retryConnect(() => mongoose.connect(uri));

连接状态事件处理:

sequelize.addHook('afterDisconnect', async () => {
  console.warn('Disconnected from DB, attempting reconnect...');
  await sequelize.authenticate();
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌