TypeORM - ManyToManyリレーションで中間テーブルにカスタムな名前付けする方法

# はじめに

TypeORMのマイグレーション機能使えば、中間テーブル名は勝手に名付けてくれるのですが、`entityA_columnA_entityB`という名前になり、ミーニングレス感が否めないテーブル名になります
またDatabaseを他プロジェクトと共有していて、マイグレーションをTypeORMでやってないパターンのとき困ったりしました（あとテーブル名に規則があったりしますよねー）
今回は、`JoinTable`デコレータを使って、カスタムな名前付けをしたテーブル・カラム名でも問題なく、マイグレーション・データ取得できる方法をメモします


## 結論

```typescript
@Entity({
  name: 'entity_a',
})
export class EntityA {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToMany(type => EntityB)
  @JoinTable({
    name: 'junction',
    joinColumn: {
      name: 'entity_a_id',
      referencedColumnName: 'id',
    },
    inverseJoinColumn: {
      name: 'entity_b_id',
      referencedColumnName: 'id',
    },
  })
  entityBs: EntityB[];
}

@Entity({
  name: 'entity_b',
})
export class EntityB {
  @PrimaryGeneratedColumn()
  id: number;
}
```

## Step by stepで見ていく

EntityA,Bでは分かりづらいので、UserエンティティとSkillエンティティ、その多対多リレーションを実現する中間テーブルを考えます

### まずTypeORMマイグレーションでやってみると
UserエンティティとSkillエンティティを準備します

```typescript
@Entity({
  name: 'users',
})
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 32 })
  name: string;

  @Column({ length: 32 })
  email: string;

  @ManyToMany(type => Skill)
  @JoinTable()
  skills: Skill[];
}

@Entity({
  name: 'skills',
})
export class Skill {
  @PrimaryGeneratedColumn()
  code: number;

  @Column({ length: 32 })
  name: string;
}
```

これをマイグレーションするとこんなテーブルができました

```sh

mysql> show tables;
+---------------------+
| Tables_in_database  |
+---------------------+
| migrations          |
| skills              |
| users               |
| users_skills_skills |
+---------------------+
```

確認のためこんなコードを書きました

```typescript
import "reflect-metadata";
import { createConnection } from "typeorm";
import { Skill } from "./skill.master";
import { User } from "./user.entity";

// npx ts-node ./src/entities/test.ts 

createConnection().then(async connection => {
  const skill = new Skill();
  skill.name = 'TypeORM';

  await connection.manager.save(skill);

  const user = new User();
  user.name = 'karino';
  user.email = 'email';
  user.skills = [skill];

  await connection.manager.save(user);

  const result = await connection.createQueryBuilder()
    .select('users')
    .from(User, 'users')
    .leftJoinAndSelect('users.skills', 'skill')
    .orderBy('id', 'DESC')
    .getOne();

  console.log(result);
}).catch(error => console.log(error));

// User {
//  id: 7,
//  name: 'karino',
//  email: 'email',
//  skills: [ Skill { code: 9, name: 'TypeORM' } ]
// } 
```

### ではTypeORMでマイグレーションしたものを`JoinTable`のプロパティで再現する
```typescript

  @ManyToMany(type => Skill)
  @JoinTable()
  skills: Skill[];

  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  @ManyToMany(type => Skill)
  @JoinTable({
    name: 'users_skills_skills',
    joinColumn: {
      name: 'usersId',
      referencedColumnName: 'id',
    },
    inverseJoinColumn: {
      name: 'skillsCode',
      referencedColumnName: 'code',
    },
  })
  skills: Skill[];
```

- `JoinTable`の`name`に設定したいテーブル名(`users_skills_skills `)を当てます
- `joinColumn`には、このエンティティ側の設定を記述します
    - `name`には中間テーブル側のカラム名
    - `referencedColumnName`にはエンティティ側のカラム名
- `inverseJoinColumn`には関連付けたいエンティティ側の設定を記述します
    - `name`, `referencedColumnName`共に同じ要領で設定

この状態でマイグレーションしても、まったく同じマイグレーションファイルが生成されることを確認しました
確認用のテストコードも変更なく問題なく動作しました

### 最後にカスタムなテーブル名を付けてみる
- 中間テーブル名は、`user_skills`
- `user_id`, `skill_code`のカラムを持つ

ようにしたいです。やってみましょう

```typescript
  @JoinTable({
-   name: 'users_skills_skills',
+   name: 'users_skills',
    joinColumn: {
-     name: 'usersId',
+     name: 'user_id',
      referencedColumnName: 'id',
    },
    inverseJoinColumn: {
-     name: 'skillsCode',
+     name: 'skill_code',
      referencedColumnName: 'code',
    },
  })
```

分かってみるとシンプルですが、最初はややこしかったです。ORMはActiveRecordしか知らないので相場がわかりません
確認用のテストコードも変更なく問題なく動作しましたが、マイグレーションしてみると新しく`users_skills`テーブルがCreateされて、`users_skills_skills`に変更はありませんでした
まあ手動でマイグレーションファイルを書き換えればいいレベルだと思います

### 余談：中間テーブルにも`cteatedAt`とかカスタムなプロパティを入れたいんだけど…
- [Adding extra columns to a join table/relationship table](https://github.com/typeorm/typeorm/issues/2402#issuecomment-399895762)
- [typeorm - how to add extra field to “many to many” table?](https://stackoverflow.com/questions/55253563/typeorm-how-to-add-extra-field-to-many-to-many-table)

TypeORMの機能としては、エンティティを作らない方法では、プロパティを追加することは出来ないようです
新しくエンティティを定義してあげる必要があります


### References
- https://github.com/typeorm/typeorm/blob/master/docs/relations.md
- https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md