repositories - ZenORM 自动生成 Repository 类
本模块由 ZenORM 代码生成器(zenorm-generate)自动生成,为每个数据库表对应的 Model 类提供完整的 CRUD 操作方法。
通过 bindQuery 机制将 MySQL 连接池绑定到 ORM 模型,使得在业务代码中可以直接使用 Model 类的静态方法进行数据库操作,无需手动管理连接。
注意
_repositories.ts 文件为自动生成文件,每次重新生成数据库结构时会被覆盖,请不要手动修改此文件。如需扩展模型方法,请在对应的模型文件中添加。
前置条件
确保已安装 MySQL 模块和 ZenORM 相关依赖:
# 生产依赖
npm install @zenweb/mysql zenorm
# 开发依赖(用于代码生成)
npm install --save-dev @zenorm/generate @zenorm/generate-mysql
bindQuery 机制
bindQuery 是连接 MySQL 连接池与 ZenORM Repository 的桥梁。在 MySQL 模块初始化时,将连接池实例传递给 ORM 生成的 bindQuery 函数,之后所有 Repository 的静态方法都会自动使用该连接池。
单数据库配置
src/index.ts
src/index.ts
import { create } from 'zenweb';
import modMySQL from '@zenweb/mysql';
import { bindQuery } from './model';
create()
.setup(modMySQL({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '',
database: 'test',
charset: 'utf8mb4',
timezone: '+08:00',
connectionLimit: 100,
// 将连接池绑定到 ORM
bindQuery,
}))
.start();
多租户配置
在使用 @zenweb/tenant 多租户模块时,bindQuery 支持动态切换数据库连接:
src/index.ts
import { create } from 'zenweb';
import modTenant from '@zenweb/tenant';
import { bindQuery } from './model';
create()
.setup(modTenant({
tenantGetter: ctx => {
// 根据请求获取租户信息
return tenantsConfig[ctx.host];
},
pools: {
S1: {
MASTER: {
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '123456',
charset: 'utf8mb4',
timezone: '+08:00',
connectionLimit: 100,
},
},
S2: {
MASTER: {
host: '127.0.0.1',
port: 3307,
user: 'root',
password: '123456',
charset: 'utf8mb4',
timezone: '+08:00',
connectionLimit: 100,
},
},
},
bindQuery, // ORM 查询绑定
}))
.start();
自动生成的 Repository 结构
运行 npm run dbgen 后,会在 model/_repositories.ts 中生成类似以下结构的代码:
src/model/_repositories.ts(自动生成)
import { createRepositoryQuery, QueryParam } from 'zenorm';
import * as _tables from './_tables';
import _User from './user';
let _bindQuery: QueryParam;
function _query() { return typeof _bindQuery === 'function' ? _bindQuery() : _bindQuery }
/** 绑定模型 Query 源 */
export function bindQuery(query: QueryParam) { _bindQuery = query; }
export class User extends _User {
/** 使用指定 Query 对象查询 UserRepository */
static query = createRepositoryQuery<User, _tables.UserTable, number>(User);
/** Query 绑定的 UserRepository */
static repository = User.query(_query);
static find: typeof User.repository.find = User.repository.find.bind(User.repository);
static findByPk: typeof User.repository.findByPk = User.repository.findByPk.bind(User.repository);
static getByPk: typeof User.repository.getByPk = User.repository.getByPk.bind(User.repository);
static count: typeof User.repository.count = User.repository.count.bind(User.repository);
static exists: typeof User.repository.exists = User.repository.exists.bind(User.repository);
static create: typeof User.repository.create = User.repository.create.bind(User.repository);
static createAndGet: typeof User.repository.createAndGet = User.repository.createAndGet.bind(User.repository);
static getOrCreate: typeof User.repository.getOrCreate = User.repository.getOrCreate.bind(User.repository);
static save: typeof User.repository.save = User.repository.save.bind(User.repository);
static update: typeof User.repository.update = User.repository.update.bind(User.repository);
static delete: typeof User.repository.delete = User.repository.delete.bind(User.repository);
/** 保存当前实例数据 */
save() { return User.repository.save(this); }
/** 更新当前实例数据 */
update(data: Partial<User>) { return User.repository.update(this, data); }
/** 删除当前实例数据 */
delete() { return User.repository.delete(this); }
}
Repository 方法说明
查询方法
| 方法 | 说明 | 返回值 |
|---|---|---|
find(conditions?) | 条件查询,返回 QueryBuilder | QueryBuilder |
findByPk(pk) | 按主键查询 | Promise<Model \| null> |
getByPk(pk) | 按主键查询,不存在则抛出异常 | Promise<Model> |
count(conditions?) | 统计记录数 | Promise<number> |
exists(conditions) | 判断记录是否存在 | Promise<boolean> |
写入方法
| 方法 | 说明 | 返回值 |
|---|---|---|
create(data) | 创建记录 | Promise<Model> |
createAndGet(data) | 创建并获取完整记录 | Promise<Model \| null> |
getOrCreate(conditions, data) | 查询或创建记录 | Promise<Model> |
save(instance) | 保存实例(新增或更新) | Promise<Model> |
update(instance, data) | 更新实例指定字段 | Promise<Model> |
delete(instance) | 删除记录 | Promise<void> |
使用示例
在 Service 中使用
src/service/chat-room.ts
import { Context, Inject } from 'zenweb';
import { ChatRoom, ChatRoomMember, ChatRoomMessage, User } from '../model';
export class ChatRoomService {
@Inject ctx!: Context;
/**
* 创建聊天室
*/
async create(name: string) {
const userId = this.ctx.user!.id!;
// 使用 createAndGet 创建并获取完整记录
const room = await ChatRoom.createAndGet({
name,
creator_id: userId,
});
if (!room) {
throw new Error('创建聊天室失败');
}
// 使用 create 创建成员记录
await ChatRoomMember.create({
room_id: room.id!,
user_id: userId,
role: 'owner',
});
return room;
}
/**
* 加入聊天室
*/
async join(roomId: number) {
const userId = this.ctx.user!.id!;
// 使用 getByPk 查询,不存在会抛出异常
const room = await ChatRoom.getByPk(roomId);
// 使用 find 条件查询,get() 获取单条记录
const exists = await ChatRoomMember.find({ room_id: roomId, user_id: userId }).get();
if (exists) {
throw new Error('已在聊天室中');
}
await ChatRoomMember.create({
room_id: roomId,
user_id: userId,
role: 'member',
});
return room;
}
/**
* 获取聊天室成员列表
*/
async getMembers(roomId: number) {
// 使用 find 条件查询,all() 获取所有记录
const members = await ChatRoomMember.find({ room_id: roomId }).all();
const userIds = members.map(m => m.user_id!);
// 使用 find 进行批量查询
const users = userIds.length > 0 ? await User.find({ id: userIds }).all() : [];
return members.map(m => ({
id: m.id,
room_id: m.room_id,
user_id: m.user_id,
role: m.role,
user: users.find(u => u.id === m.user_id),
}));
}
/**
* 获取聊天记录
*/
async getMessages(roomId: number, limit: number = 50) {
// 使用 find 链式调用,支持 order、limit 等方法
const messages = await ChatRoomMessage.find({ room_id: roomId })
.order('created_at', 'DESC')
.limit(limit)
.all();
return messages.reverse();
}
}
在 Controller 中使用
src/controller/chat-room.ts
import { Get, Post, Context, Injectable } from 'zenweb';
import { ChatRoomService } from '../service/chat-room';
@Injectable
export class ChatRoomController {
constructor(
private ctx: Context,
private service: ChatRoomService,
) {}
@Post()
async create() {
const { name } = this.ctx.request.body;
return await this.service.create(name);
}
@Post()
async join() {
const { roomId } = this.ctx.request.body;
return await this.service.join(roomId);
}
@Get()
async members() {
const roomId = Number(this.ctx.query.room_id);
return await this.service.getMembers(roomId);
}
}
实例方法 - 修改并保存
Repository 同时为模型实例提供 save()、update()、delete() 方法:
src/service/chat-room.ts
// 获取实例后修改属性,调用 save() 保存
async recallMessage(messageId: number) {
const message = await ChatRoomMessage.getByPk(messageId);
if (!message) {
throw new Error('消息不存在');
}
message.recalled = true;
await message.save();
return message;
}
// 使用 update() 仅更新指定字段
async muteMember(roomId: number, userId: number) {
const member = await ChatRoomMember.find({ room_id: roomId, user_id: userId }).get();
if (!member) {
throw new Error('成员不存在');
}
await member.update({ muted: true });
return member;
}
// 使用 delete() 删除记录
async kickMember(roomId: number, userId: number) {
const member = await ChatRoomMember.find({ room_id: roomId, user_id: userId }).get();
if (!member) {
throw new Error('成员不存在');
}
await member.delete();
}
静态方法 - 直接更新和删除
除实例方法外,也可以通过静态方法直接操作:
// 按主键删除
await ChatRoom.delete(room);
// 直接更新指定字段(无需先查询)
await ChatRoom.update(room, { name: '新名称' });
// 按条件查询是否存在
const exists = await ChatRoomMember.exists({ room_id: 1, user_id: 2 });
// 统计数量
const count = await ChatRoomMessage.count({ room_id: 1 });
使用事务
通过 @zenweb/mysql 的 $mysql 全局对象执行事务操作:
src/service/order.ts
import { $mysql } from '@zenweb/mysql';
import { Order, OrderItem } from '../model';
export class OrderService {
async createOrder(userId: number, items: { productId: number; quantity: number }[]) {
return await $mysql.transaction(async tx => {
// 在事务中创建订单
const order = await Order.createAndGet({
user_id: userId,
status: 'pending',
});
// 在事务中批量创建订单项
for (const item of items) {
await OrderItem.create({
order_id: order!.id,
product_id: item.productId,
quantity: item.quantity,
});
}
return order;
});
}
}
补充说明
Repository 类通过 bindQuery 绑定全局连接池,在事务场景中需要配合 $mysql.transaction() 使用,确保所有操作在同一个数据库连接上执行。