使用用户注册时创建的 ID 插入关系 table
Insert into relationship table using id created at user registration
我有两个 table,如下所示
第一个 table 是给用户的,通过客户端的注册表填写。创建新用户时,我需要在第二个 'quotas' table 中填充日期、金额,并 link 使用用户 ID。 'user_id' 用于在 GET 中拉取配额信息并显示客户端。我在创建时使用 'id' 填充第二个 table 时遇到问题。我正在使用 knex 进行所有查询。我会在 knex 中使用 join 来 link 他们吗?
服务器
hydrateRouter // get all users
.route('/api/user')
.get((req, res) => {
knexInstance
.select('*')
.from('hydrate_users')
.then(results => {
res.json(results)
})
})
.post(jsonParser, (req, res, next) => { // register new users
const { username, glasses } = req.body;
const password = bcrypt.hashSync(req.body.password, 8);
const newUser = { username, password, glasses };
knexInstance
.insert(newUser)
.into('hydrate_users')
.then(user => {
res.status(201).json(user);
})
.catch(next);
})
客户端
export default class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
glasses: 0
}
}
handleSubmit(event) {
event.preventDefault();
fetch('http://localhost:8000/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state)
})
.then(response => response.json())
.then(responseJSON => {
this.props.history.push('/login');
})
}
显示水量的服务器端路由
hydrateRouter
.route('/api/user/waterconsumed/:user_id') // display water consumed/day
.all(requireAuth)
.get((req, res, next) => {
const {user_id} = req.params;
knexInstance
.from('hydrate_quotas')
.select('amount')
.where('user_id', user_id)
.first()
.then(water => {
res.json(water)
})
.catch(next)
})
谢谢!
获取插入行的 ID
所以这是关系数据库中的一种常见模式,在您拥有下蛋的鸡的唯一 ID 之前,您无法创建鸡蛋!显然,数据库需要告诉您它想如何引用鸡。
在 Postgres 中,您可以简单地使用 Knex 的 .returning
函数来明确表示您希望在成功插入后将新行的 id
列 return 发送给您。这将使查询的第一部分如下所示:
knexInstance
.insert(newUser)
.into('users')
.returning('id')
注意:并非所有数据库都以相同的方式支持这一点。特别是,如果您恰好在本地使用 SQLite 进行开发,它将 return 受查询影响的 行数 ,而不是 id,因为 SQL网站不支持 SQL 的 RETURNING
。最好是使用 Postgres 在本地进行开发以避免令人讨厌的意外。
好的,所以我们知道我们要找的是哪只鸡。现在我们需要确保我们已经等待了正确的 id,然后继续使用它:
.then(([ userId ]) => knexInstance
.insert({ user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT })
.into('quotas')
)
或者您选择填充第二个 table。
注意:DATE
是一个 SQL 关键字。因此,它不是一个很好的列名。 created
或 updated
怎么样?
用合理的数据回应
这就是基本的 "I have the ID, let's insert to another table" 策略。但是,您实际上希望能够响应已创建的用户...对于 201 响应,这似乎是明智的 API 行为。
您不想做的是用数据库中的整个用户记录进行响应,这将公开密码哈希(就像您在第一个代码中所做的那样)阻止你的问题)。理想情况下,您可能希望使用 UI 友好的 table 组合来回应。
幸运的是,.returning
也接受数组参数。这使我们能够传递我们想要响应的列列表,从而降低意外将某些我们不想传输的内容暴露到 API 表面的风险。
const userColumns = [ 'id', 'username', 'glasses' ]
const quotaColumns = [ 'amount' ]
knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
.then(([ user]) => knexInstance
.insert({
user_id: user.id,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
.then(([ quota ]) => res.status(201)
.json({
...user,
...quota
})
)
)
Async/await 可读性
这些天,我可能会避免像这样的承诺链,转而使用 await
为我们提供的语法糖。
try {
const [ user ] = await knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
const [ quota ] = await knexInstance
.insert({
user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
res
.status(201)
.json({
...user,
...quota
})
} catch (e) {
next(Error("Something went wrong while inserting a user!"))
}
交易注意事项
这里有一些假设,但有一个很大的假设:我们假设两个插入都会成功。当然,我们提供了一些错误处理,但仍然有可能第一次插入成功,第二次失败或由于某种原因超时。
通常,我们会在一个事务块中进行多次插入。以下是 Knex 的处理方式:
try {
const userResponse = await knexInstance.transaction(async tx => {
const [ user ] = await tx.insert(...)
const [ quota ] = await tx.insert(...)
return {
...user,
...quota
}
})
res
.status(201)
.json(userResponse)
} catch (e) {
next(Error('...'))
}
对于相互依赖的多个插入来说,这是一个很好的通用做法,因为它设置了一个 "all or nothing" 方法:如果出现故障,数据库将返回到之前的状态。
我有两个 table,如下所示
第一个 table 是给用户的,通过客户端的注册表填写。创建新用户时,我需要在第二个 'quotas' table 中填充日期、金额,并 link 使用用户 ID。 'user_id' 用于在 GET 中拉取配额信息并显示客户端。我在创建时使用 'id' 填充第二个 table 时遇到问题。我正在使用 knex 进行所有查询。我会在 knex 中使用 join 来 link 他们吗?
服务器
hydrateRouter // get all users
.route('/api/user')
.get((req, res) => {
knexInstance
.select('*')
.from('hydrate_users')
.then(results => {
res.json(results)
})
})
.post(jsonParser, (req, res, next) => { // register new users
const { username, glasses } = req.body;
const password = bcrypt.hashSync(req.body.password, 8);
const newUser = { username, password, glasses };
knexInstance
.insert(newUser)
.into('hydrate_users')
.then(user => {
res.status(201).json(user);
})
.catch(next);
})
客户端
export default class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
glasses: 0
}
}
handleSubmit(event) {
event.preventDefault();
fetch('http://localhost:8000/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state)
})
.then(response => response.json())
.then(responseJSON => {
this.props.history.push('/login');
})
}
显示水量的服务器端路由
hydrateRouter
.route('/api/user/waterconsumed/:user_id') // display water consumed/day
.all(requireAuth)
.get((req, res, next) => {
const {user_id} = req.params;
knexInstance
.from('hydrate_quotas')
.select('amount')
.where('user_id', user_id)
.first()
.then(water => {
res.json(water)
})
.catch(next)
})
谢谢!
获取插入行的 ID
所以这是关系数据库中的一种常见模式,在您拥有下蛋的鸡的唯一 ID 之前,您无法创建鸡蛋!显然,数据库需要告诉您它想如何引用鸡。
在 Postgres 中,您可以简单地使用 Knex 的 .returning
函数来明确表示您希望在成功插入后将新行的 id
列 return 发送给您。这将使查询的第一部分如下所示:
knexInstance
.insert(newUser)
.into('users')
.returning('id')
注意:并非所有数据库都以相同的方式支持这一点。特别是,如果您恰好在本地使用 SQLite 进行开发,它将 return 受查询影响的 行数 ,而不是 id,因为 SQL网站不支持 SQL 的 RETURNING
。最好是使用 Postgres 在本地进行开发以避免令人讨厌的意外。
好的,所以我们知道我们要找的是哪只鸡。现在我们需要确保我们已经等待了正确的 id,然后继续使用它:
.then(([ userId ]) => knexInstance
.insert({ user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT })
.into('quotas')
)
或者您选择填充第二个 table。
注意:DATE
是一个 SQL 关键字。因此,它不是一个很好的列名。 created
或 updated
怎么样?
用合理的数据回应
这就是基本的 "I have the ID, let's insert to another table" 策略。但是,您实际上希望能够响应已创建的用户...对于 201 响应,这似乎是明智的 API 行为。
您不想做的是用数据库中的整个用户记录进行响应,这将公开密码哈希(就像您在第一个代码中所做的那样)阻止你的问题)。理想情况下,您可能希望使用 UI 友好的 table 组合来回应。
幸运的是,.returning
也接受数组参数。这使我们能够传递我们想要响应的列列表,从而降低意外将某些我们不想传输的内容暴露到 API 表面的风险。
const userColumns = [ 'id', 'username', 'glasses' ]
const quotaColumns = [ 'amount' ]
knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
.then(([ user]) => knexInstance
.insert({
user_id: user.id,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
.then(([ quota ]) => res.status(201)
.json({
...user,
...quota
})
)
)
Async/await 可读性
这些天,我可能会避免像这样的承诺链,转而使用 await
为我们提供的语法糖。
try {
const [ user ] = await knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
const [ quota ] = await knexInstance
.insert({
user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
res
.status(201)
.json({
...user,
...quota
})
} catch (e) {
next(Error("Something went wrong while inserting a user!"))
}
交易注意事项
这里有一些假设,但有一个很大的假设:我们假设两个插入都会成功。当然,我们提供了一些错误处理,但仍然有可能第一次插入成功,第二次失败或由于某种原因超时。
通常,我们会在一个事务块中进行多次插入。以下是 Knex 的处理方式:
try {
const userResponse = await knexInstance.transaction(async tx => {
const [ user ] = await tx.insert(...)
const [ quota ] = await tx.insert(...)
return {
...user,
...quota
}
})
res
.status(201)
.json(userResponse)
} catch (e) {
next(Error('...'))
}
对于相互依赖的多个插入来说,这是一个很好的通用做法,因为它设置了一个 "all or nothing" 方法:如果出现故障,数据库将返回到之前的状态。