typeormで楽観排他やってみた。


前回に続いてtypeorm記事です。
@VersionColumn()と、setLock("optimistic", version)の動作確認をします。

## 排他について
調べれば出てきますが参考になったものをあげておきます。

こちらの資料はとても丁寧に記載されている。
https://terasolunaorg.github.io/guideline/5.3.0.RELEASE/ja/ArchitectureInDetail/DataAccessDetail/ExclusionControl.html
上記が長く、概要だけ理解したければ以下
https://mintaku-blog.net/exclusion-control/

## 結果
バージョンのインクリメントが行われ、バージョンに違いがあればエラーとなることを確認

## コード
### entity
```
@Entity()
export class Category {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany((type) => Question, (question) => question.categories)
  questions: Promise<Question[]>;
}
```

```
@Entity()
export class Question {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  text: string;

  @ManyToMany((type) => Category, (category) => category.questions)
  @JoinTable()
  categories: Promise<Category[]>;

  @VersionColumn()
  version: number;
}
```
### controller
```
@Controller("test")
export class QuestionController {
  constructor(private dataSource: DataSource) {}

  @Post("question")
  async create() {
    const category1 = new Category();
    category1.name = "animals";
    await this.dataSource.manager.save(category1);

    const category2 = new Category();
    category2.name = "zoo";
    await this.dataSource.manager.save(category2);

    const question = new Question();
    question.categories = Promise.resolve([category1, category2]);
    question.title = "aaa";
    question.text = "bbb";
    await this.dataSource.manager.save(question);

    const [questions] = await this.dataSource.getRepository(Question).find();
    const categories = await question.categories;
    console.log([questions]);
  }

  @Post("question2")
  async update() {
    //上で登録したデータを画面に表示していると想定。
    const version = 1;
    const id = 1;

    const users = await this.dataSource
      .getRepository(Question)
      .createQueryBuilder("Question")
      .setLock("optimistic", version)
      .getMany();

    await this.dataSource
      .createQueryBuilder()
      .update(Question)
      .set({ title: "Timber" })
      .where("id = :id", { id: id })
      .execute();

    const question = new Question();
    const [questions] = await this.dataSource.getRepository(Question).find();
    const categories = await question.categories;
    console.log([questions]);
  }
}

```
### 実行ログ
/test/question
```
query: START TRANSACTION
query: INSERT INTO "category"("name") VALUES ($1) RETURNING "id" -- PARAMETERS: ["animals"]
query: COMMIT
query: START TRANSACTION
query: INSERT INTO "category"("name") VALUES ($1) RETURNING "id" -- PARAMETERS: ["zoo"]
query: COMMIT
query: START TRANSACTION
query: INSERT INTO "question"("title", "text", "version") VALUES ($1, $2, 1) RETURNING "id", "version" -- PARAMETERS: ["aaa","bbb"]
query: INSERT INTO "question_categories_category"("questionId", "categoryId") VALUES ($1, $2), ($3, $4) -- PARAMETERS: [1,1,1,2]
query: COMMIT
query: SELECT "Question"."id" AS "Question_id", "Question"."title" AS "Question_title", "Question"."text" AS "Question_text", "Question"."version" AS "Question_version" FROM "question" "Question"
[ Question { id: 1, title: 'aaa', text: 'bbb', version: 1 } ]
```
test/question2
1回目(成功)
```
query: SELECT "Question"."id" AS "Question_id", "Question"."title" AS "Question_title", "Question"."text" AS "Question_text", "Question"."version" AS "Question_version" FROM "question" "Question"
query: UPDATE "question" SET "title" = $1, "version" = "version" + 1 WHERE "id" = $2 -- PARAMETERS: ["Timber",1]
query: SELECT "Question"."id" AS "Question_id", "Question"."title" AS "Question_title", "Question"."text" AS "Question_text", "Question"."version" AS "Question_version" FROM "question" "Question"
query: SELECT "categories"."id" AS "categories_id", "categories"."name" AS "categories_name" FROM "category" "categories" INNER JOIN "question_categories_category" "question_categories_category" ON "question_categories_category"."questionId" IN ($1) AND "question_categories_category"."categoryId"="categories"."id" -- PARAMETERS: [null]
[ Question { id: 1, title: 'Timber', text: 'bbb', version: 2 } ]
```
2回目(失敗)
```
query: SELECT "Question"."id" AS "Question_id", "Question"."title" AS "Question_title", "Question"."text" AS "Question_text", "Question"."version" AS "Question_version" FROM "question" "Question"
[Nest] 18929  - 2023/07/25 20:24:27   ERROR [ExceptionsHandler] The optimistic lock on entity Question failed, version 1 was expected, but is actually 2.
OptimisticLockVersionMismatchError: The optimistic lock on entity Question failed, version 1 was expected, but is actually 2.
```