数据库连接与ORM集成
数据库连接的基本方式
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); // 反向关联
模型验证规则如allowNull
和validate
在数据持久化前自动生效。关联方法如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
上一篇:WebSocket集成方案
下一篇:安全防护与最佳实践