---
# System prepended metadata

title: TypeORM
tags: [ORM, SQL]

---

# TypeORM

### 【官方】
中文官方（資料比英文官方舊）：
https://typeorm.devjs.cn/
英文官方
https://typeorm.io/

參考文檔： 
https://typeorm.biunav.com/en/connection-options.html#postgres-cockroachdb-connection-options

https://stackoverflow.com/questions/67659981/typeorm-create-method-is-extremely-slow

### 兩種資料對應資料庫模式【Active Record】&【Data Mapper】
#### Active Record 將方法寫在 entity內，適用於小、靈活的系統
#### Data Mapper 將方法寫在Repository，適用大型、維護量大的系統

https://hackmd.io/@LouisChen13/ByhUhn-1j

### 【Lock】
實作：
https://stackoverflow.com/questions/69012855/typeorm-database-lock-please-explain-how-to-use-database-lock-in-typeorm-using



### 【loadRelationIds】

遇過一種狀況是，message表與user表是多對一的關係，因此有設定關聯。當client端要修改某一條message時，我們使用messageID去message表去查找該筆資料，同時要確認該client是否為該message的擁有者：

`const checkUser = await userMessage.findOneBy({usermessageId})`

這樣找出來的資料會發現因為message entity中，message關聯到user是關聯整個user並不是手動記錄下userId在message表中，所以checkUser並不會response userId的欄位，那我們可能會想說把它relation關聯出來就有user的id了，那我們可以改寫成以下：

```
const checkUser = await userMessage.findOne({
    relations:['User'],
    where:{id:usermessageId}
})
```

這樣的結果是OK的，確實可以確認操作登入的client確實是該message所屬的user沒錯，但是如果為了要取得user的id而去把user關聯出來，且明明就是有設定關聯的，FK是存在於message表的，這樣不會太浪費效能？於是我們改寫成以下：

```
const toMessage = await this.userMessageRepository.findOne({
      loadRelationIds: {
        relations: ['toUser'],
        disableMixedMap: true,
      },
      where: { id: userMessageId, toUser: { id: userId } },
    });
```

loadRelationIds只會將關聯的userId load出來，且因為userId本來就是有FK是存在在userMessage的！

disableMixedMap的作用是會將撈回來的資料依照原定的階層排，若關掉的話，會直接將關聯的userId拉回跟usermessage同一個階層


### 【QueryRunner】
如果 RDBMS 支持連接池，則每個新的 QueryRunner 實例都從連接池中獲取一個連接。 對於不支持連接池的數據庫，跨數據源使用同一個連接。

連接池指就是，當我們連線資料庫時，都要耗費蠻多效能，連接池會將我們的連線先放在連接池中，需要用到時，會在從連接池中獲取連線，就不用再連接一次，QueryRunner的用法是每個queryRunner會建立獨立的連接實例，這樣就可以做到transaction?

### 【QueryBuilder】
TypeORM Repository已經寫好常用對資料庫新增、修改、搜尋(find說明文件)、刪除資料。

如果要更細部的建立Query，TypeORM提供Query Builder相關API，以API的方式去組SQL Query，比較彈性。

https://ithelp.ithome.com.tw/articles/10207509

QueryBuilder的一些用法
https://juejin.cn/post/7239135597478379576

### 【Unique column 和 @Unique的差別】
@Unique適用於整個entity，當然也可以多列的去約束其為unique。
https://stackoverflow.com/questions/63793417/typeorm-whats-difference-between-unique-decorator-and-unique-true-in-col

### 【Repository】
Repository是typeorm提供的已經幫我們包好的很多方法，點進去Repository就知道了

### 【關聯欄位】
關聯選項可參考：
https://orkhan.gitbook.io/typeorm/docs/relations

https://blog.errorbaker.tw/posts/minw/nest-js-migration/

#### [@ManyToOne()參數用法解說]

第一個參數是個函式，返回關聯的類
第二個參數創建雙向關係
https://blog.csdn.net/qq1195566313/article/details/128379605

#### [@ManyToMany()]
manyToMany的話

#### [@joinColumn用法]
@joinColumnu定義哪一側會顯示關聯欄位，所以正常狀況下，只有在ManyToOne那邊會有joinColumn，也可以自定義關聯欄位的名稱。

#### [Eager]
eager true會連同關聯的欄位的詳細資料一起找出來

### 【Entity】
設定entity時，正常會因為我們設定的型別，typeORM會去做隱式轉換，但因typeORM看不懂聯合型別，所以要特別使用decorator @Type(()=>Date)去轉換成正確的型別。



### 【Like】

Like 'asd%'：查找以“asd”開頭的任何值
Like '%asd'：查找以“asd”結尾的任何值
Like '%asd%'：查找包含“asd”的任何值（在任何位置）

```
const posts = await postRepository.find({
      where: { title: Like('%asd%') }
```
#### 【Find 選項】
查找的語法：
https://typeorm.biunav.com/zh/find-options.html#%E5%9F%BA%E7%A1%80%E9%80%89%E9%A1%B9

查找的選項設定：
https://hi-founder.com/p/typeorm-repository/

##### 【In、Not、MoreThanEual】
In用於查找是否在陣列裡，不是orm特有是SQL的東西，ORM可以照以下這樣寫：

    const list = await this.listRepository.find({
      where: { id: Array.isArray(id) ? In(id) : id },
      relations,
    });

Not用於查找不是在陣列裡的

 FindOptionsRelations 



### 【Migration】
使用cli去migration，要先編譯成js檔，可使用ts-node套件，直接使用ts檔（會先用你的tsconfig幫你編譯好，再去跑指令要跑的檔案)

若需要migration:run的話，需要透過以下指令告知datasource
![](https://i.imgur.com/l0aP7fY.png)
https://typeorm.io/migrations#

以下方法會報錯
https://stackoverflow.com/questions/71625087/typeorm-migration-file-must-contain-a-typescript-javascript-code-and-export-a

![](https://i.imgur.com/g4VQP0P.png)


### 【錦集】

Q1:使用create()資料庫沒東西？但有response回來，並且他有提示說create()這個方法不用await(他寫說'await' 對此運算式的類型沒有作用)?用save()資料庫就可成功寫入資料庫，不論是用new()或是直接存入的方法，都是create()不行，save()可以。

A1:create()的用法，是會自動幫你new一個實例出來，方便的寫法可以寫類似以下：

```
async creatUser(createUserDto:any){
    const user = this.userRepository.create({createUserDto})
    await this.userRepository.save(user)
    return user //看要回傳什麼
}

```

Q2:updateListDTO中直接extends createListDTO的屬性，那我createListDTO的屬性不設定為唯獨(readonly)是可以的嗎？安全嗎？
A2:最好是不要，最好DTO就是指for前端送過來的東西，不要另外在controller裡修改，只能這樣的時候，也可以在API文件不要顯示此欄位。

Q3:通常DTO會把foreign key也寫進去嗎？（以前的作法有，但要思考一下真的要加嗎？可能要看整個schema？這次沒有加，因在body內不會接到currentUser的ID，我放在jwt裡面，因此input用到DTO的地方不需要）
A3:通常會由controller傳2個參數到service那邊，不用另外放在DTO裡面。

npx cli（創建migration）
https://stackoverflow.com/questions/72594414/npx-typeorm-migrationcreate-n-testdata-dosent-create-a-migration

使用migration方法
https://www.linkedin.com/pulse/nestjs-typeorm-migrations-nestor-iv%C3%A1n-scoles/

創建方法：
https://hackmd.io/@LouisChen13/ByhUhn-1j

Active Record VS Data Mapper
https://culttt.com/2014/06/18/whats-difference-active-record-data-mapper

Q:設定手動關聯(只記錄FK的ID)的缺點？

A:只手動記錄FK的話，對資料庫來說不算是真正的關聯，操作上，DB會去檢查新增資料時，若你新增的FK是沒有此FK的，就會出問題，在刪除欄位時，也會不能被刪除，也無法做到CASCADE。

ORM用法中，也會無法將關聯欄位搜尋出來（除非自己寫QueryBuilder 或執行RawSQL）

### 【問題】


Q1:另有看到有人使用mongoDB時，加密部分是寫在model裡面，這樣好處是？ (0:17)
https://www.bilibili.com/video/BV1sJ411n7PE/?spm_id_from=333.337.search-card.all.click&vd_source=107ba761a291ed2746522372f562d92e


Q2:他還有一個蠻好的寫法是把password直接在model中寫selet:false就可以不管在‘取得單一使用者’‘取得所有使用者’都可以一起直接把password選擇不回傳？(可以對應到我的哪個部分嗎？)


Q4:直接從SQLite 看到的foreign key的欄位名稱跟return查出來的不一樣?(換成postgreSQL看還有沒有此問題)

![](https://i.imgur.com/Vdsktz3.png)

![](https://i.imgur.com/itlSHzR.png)









