对应内容:#19 DataLoader & # 20 Graphql function
主要内容:
- 使用DataLoader 来合并数据库请求,减少访问数据库的次数
- 在前端使用函数代替写死的查询参数
问题思考
场景一
我们在之前的代码中应该明白这样一个流程:查询所有的n个活动,并返回一个列表,如果活动中需要用到创建者,那么就会再次访问n次数据库,来获得n个活动对应的创建者,这样就造成了1 + n次查询。但我们是不是其实可以把这n次查询合并成一次呢?考虑这个问题。
场景二
比如一个用户分别在五个订单买了两个商品,我们在获取订单列表的时候,就要获取这个用户id下的5个订单,但是如果我们继续获取这五个订单对应的商品,就要再访问五次数据库,但是这五次访问中,获得的商品id分别是,001 001 001 002 002,那么这五次查询其实就可以合并为两次,[001, 002]
,再把这次的访问结果分发给另外对应的三次查询。这样也能减少数据库访问的压力。
1 dataloader
视频中在处理嵌套查询的时候采用的是bind方法,返回这个函数,而不执行,我用的方法是直接构造一个返回函数的函数,再返回这个函数,还有一种方法是,直接简写成箭头函数,即用即写。这个dataloader的返回值比较严苛,我的方法可能需要小小的改动,所以我就直接使用第三种方法了。
const DataLoader = require('dataloader');
const Event = require("../../models/events");
const User = require("../../models/user");
const eventLoader = new DataLoader(eventIds => {
return getEventsById(eventIds);
});
const userLoader = new DataLoader(userIds => {
return User.find({ _id: { $in: userIds}});
})
const getEventsById = async eventIds => {
try {
const events = await Event.find({ _id: { $in: eventIds } });
return events.map(event => {
return transformEvent(event);
})
} catch (error) {
throw error;
}
};
const getSingleEvent = async eventId => {
try {
return await eventLoader.load(eventId.toString());
} catch (error) {
throw error;
}
}
const getUserById = async userId => {
try {
const user = await userLoader.load(userId.toString());
return {
...user._doc,
password: null,
createEvents: async () => await eventLoader.loadMany(user.createEvents)
};
} catch (error) {
throw error;
}
};
const transformEvent = event => {
try {
return {
...event._doc,
date: event.date.toISOString(),
creator: () => getUserById(event.creator)
}
}catch (err) {
throw err;
}
};
const transformBooking = booking => {
return {
...booking._doc,
user: () => getUserById(booking.user),
event: async () => getSingleEvent(booking.event),
createdAt: booking.createdAt.toISOString(),
updatedAt: booking.updatedAt.toISOString(),
}
};
exports.getUserById = getUserById;
exports.getEventsById = getEventsById;
exports.getSingleEvent = getSingleEvent;
exports.transformEvent = transformEvent;
exports.transformBooking = transformBooking;
这里要注意一个大问题,经常会报错,返回的值的长度与输入不相同。
这里可以理解为一个槽点了,因为在之前,想要返回一个id的时候需要对它进行toString一下的,以为数据库查出来的id是一个IdObject对象。但是后来graph对其进行了修改,当你查询的时候,不需要专门处理其id了,直接把这个对象丢进去,即可自动根据这个对象,转换成字符串类的id。
但是这里不行,是因为我之前提到了一个合并的问题,比如五个查询,id分别是 001 001 001 002 002,如果直接丢进去,比较的是五个id的引用,因为他们是IdObject类型,不是原始类型,所以认为他们五个是不一样的,但是返回的时候,发现其中三个都是一样的,最后只返回两个,所以就出现了上述的问题。所以这里要你对id进行toString一下,变成string这个原始类型,这时候五个就可以合并成两个了,最后输出也是两个。