TypeORM AfterInsert() не может добавить идентификатор пользователя в другую таблицу

#node.js #typeorm

#node.js #typeorm

Вопрос:

У меня есть объект user:

 @Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  //...

  @OneToOne(() => UserActive)
  userActive: UserActive;

  @AfterInsert()
  public async handleAfterInsert() {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = this;
    await getConnection().getRepository(UserActive).save(userActive);
  }
}
 

и UserActive :

 export class UserActive extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({
    length: 25,
    unique: true,
  })
  token: string;

  @OneToOne(() => User)
  @JoinColumn()
  user: User;
}
 

Мне нужно вставить модуль token и пользователя id UserActive . this в строке userActive.user = this; находится объект user (конечно, с id ).

Когда я вставляю нового пользователя, тогда TypeORM возвращает ошибку:

Ключ (идентификатор пользователя)=(438dc281-3f03-45b0-bee8-76aecc894d14) отсутствует в таблице «пользователь».

Конечно, когда я меняю на:

 userActive.user = '438dc281-3f03-45b0-bee8-76aecc894d14'
 

тогда все работает правильно.

Я использую Next.js

Редактировать:

Я вставляю нового пользователя с помощью:

 const connection = getConnection().manager;

const user = new User();
user.name = name;
user.email = email;
user.password = await bcrypt.hash(password, 10);
await connection.save(user);
 

И я меняю метод handleAfterInsert на:

 @Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;
  
   //...

  @OneToOne(() => UserActive)
  userActive: UserActive;

  @AfterInsert()
  public async handleAfterInsert() {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = this;
    await getConnection().manager.save(userActive);
  }
}
 

Но Nest возвращает ту же ошибку, что и раньше.

РЕДАКТИРОВАТЬ 2:

Я обнаружил эту проблему: получение идентификатора в операции @AfterInsert

И я пытаюсь изменить свой метод AfterInsert на:

 @EventSubscriber()
export class UserSubscriber
  implements EntitySubscriberInterface<User> {
  listenTo() {
    return User;
  }

  async afterInsert(event: InsertEvent<User>) {
    const userActive = new UserActive();
    userActive.token = randomString();
    userActive.user = event.entity;
    await getConnection().getRepository(UserActive).save(userActive);
  }
}
 

Но теперь в таблицу ничего не добавляется user active , только добавляется новый пользователь в таблицу user .

Комментарии:

1. Пожалуйста, обновите свой вопрос, чтобы включить объект UserActive и @JoinColumn .

2. @Edward Я обновляю свой пост

Ответ №1:

Я подозреваю, что это связано с тем, что save(user) и save (userActive) выполняются в отдельных транзакциях базы данных, поэтому пользователь еще не был зафиксирован в базе данных при выполнении save (userActive) — даже если у него сгенерирован идентификатор, он еще не зафиксирован — поэтому save (Транзакция UserActive) не может видеть нового пользователя, поэтому вы видите ошибку «Ключ (userId = xxx) отсутствует в таблице user».

Возможно, вы сможете это исправить, но используя entity manager вместо репозитория. Вы не можете использовать репозиторий, потому что он ограничен одним объектом, но здесь вы имеете дело с 2 объектами (user amp; userActive).

Измените ваши 2 сохранения, чтобы использовать один и тот же диспетчер сущностей, что-то вроде этого:

Сохранение пользователя:

 const connection: Connection = await createConnection();
const manager = connection.manager;
var user = new User();
user.Name = "John";
// etc
// N.B. Use entity manager, not repository, to save: 
await manager.save(user);
 

и аналогично сохранение записи userActive:

 @Entity()
export class User extends BaseEntity {
// ...
    @AfterInsert()
    public async handleAfterInsert() {
        const userActive = new UserActive();
        userActive.token = randomString();
        userActive.user = this;
        // N.B. Use entity manager, not repository, here: 
        await getConnection().manager.save(userActive);
    }
 

ОБНОВЛЕННЫЙ ОТВЕТ

Я проверил свой ответ выше, и он действительно не работает — если я включу ведение журнала typeorm, я увижу, что там есть две отдельные транзакции.

Я нашел ответ в том же сообщении, что и вы: далее говорится, что вы должны включить опцию подключения «подписчики» — это совсем не очевидно.

Я сделал это так: создайте ‘afteruser.ts’ в папке ‘subscriber’ в проекте и убедитесь, что эта папка включена в параметр подключения ‘subscribers’ в ormconfig.json:

 {
  // ...
  "logging": true,
  "entities": [ "entity/*.js" ],
  "subscribers": [ "subscriber/*.js" ],
  // ...
}
 

Я подозреваю, что проблема в вашем обновленном решении заключается в этой строке, потому что это создает новую транзакцию:

 await getConnection().getRepository(UserActive).save(userActive);
 

Вам нужно использовать тот же «диспетчер сущностей», например:

 await event.manager.getRepository(UserActive).save(userActive);
 

Комментарии:

1. Если это сработает для вас, я отредактирую свой ответ, чтобы удалить «я подозреваю».

2. @michal Я тестировал, и действительно, мое первоначальное решение не сработало, даже если мое объяснение правильное. Как вы обнаружили, в handleAfterInsert есть известная ошибка, заключающаяся в том, что он запускает новую транзакцию, поэтому вместо этого вам нужно использовать EventSubscriber . Возможно, вы пропустили, что вам нужно event.manager, чтобы получить ту же транзакцию, что и основное сохранение. Кроме того, убедитесь, что вызывается подписчик события, добавив точку останова или войдя в консоль.

Ответ №2:

У меня такая же проблема, по некоторым причинам слушатели, подобные @AfterInsert которым, выполняются до фиксации транзакции.

Чтобы решить эту проблему, вам нужно использовать подписчиков https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md

И выполняйте свои транзакции с entity.manager.getRepository(Entity).find(...)

Ответ №3:

Вы можете удалить await ключевое слово и сделать его асинхронным, но оно запускает другую транзакцию, которая может привести к непредвиденным ошибкам, если у вас высокий трафик записи.

В документах указано, что Note: Do not make any database calls within a listener, opt for subscribers instead.

Сделайте свои upserts после вставки, используя подписчиков, в противном случае прослушиватели могут вести себя неожиданно, например, запускать другую транзакцию до завершения первой, используя подписчиков, вы можете как бы перехватывать транзакции перед фиксацией, что означает, что если вы хотите создать дочернюю сущность после создания родительской, вы можете сделать это тем же способомтранзакция.