使用用户注册时创建的 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 关键字。因此,它不是一个很好的列名。 createdupdated 怎么样?

用合理的数据回应

这就是基本的 "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" 方法:如果出现故障,数据库将返回到之前的状态。