TypeScript已經成爲一個強大的Web應用程序開發環境,在與標準JavaScript保持一致的同時,提供了顯著的改進。在本文中,我們將深入探討使用TypeScript相關的細節,創建一個強大的解決方案來管理服務器端TypeScript的數據庫訪問。
要點:
- TypeScript是JavaScript的超集,具有嚴格的語法和類型。
- TypeScript打破了JavaScript在現代大型應用程序中的應用限制。
- Decorator是一種實驗性的特性,用於註解類聲明、方法、訪問器、屬性或其他註解。
- TypeScript在TIOBE編程語言索引中排名前50。
- TypeORM包提供了對象到關係的映射來訪問支持TypeScript的關係數據庫,比如MySQL數據庫。
JavaScript是一門基於ECMAScript規範的腳本語言。JavaScript已經從客戶端腳本語言發展成爲同時可在客戶端和服務器端運行的腳本語言。服務器端JavaScript最引人注目的實現是Node.js。
問題
JavaScript缺乏大型現代Web應用程序可使用的特性,比如類型註釋、編譯時類型檢查和類型推斷,等等。JavaScript代碼在大型應用程序中變得非常複雜。
解決方案
TypeScript是JavaScript的類型化超集,旨在打破JavaScript在大型應用程序中的一些限制。
TypeScript是嚴格的JavaScript語法超集,加入了諸如編譯時類型檢查、類型註釋、類型推斷、類型擦除等特性,還支持接口和麪向對象特性。TypeScript是一門開源的腳本語言,可以被轉換爲JavaScript。轉換後的輸出就是常見的JS,而不是隻有機器才能讀取的東西。
TypeScript加入了一個叫作Decorator的實驗性特性,可以使用註釋和元編程語法給類和類成員添加額外的特性。Decorator採用了@expression的聲明方式,其中expression作爲在運行時被調用的函數,並帶有相關的聲明信息。Decorator可用於註解類的聲明、方法、訪問器、屬性或其他註解。本文中將會使用Decorator。
2012年推出的TypeScript最近大受歡迎。最近發佈的一份有關JavaScript和Web開發的InfoQ趨勢報告指出,“TypeScript的受歡迎程度急劇上升,目前已進入GitHub最受歡迎前10種編程語言之列……”2018年6月,TypeScript首次出現在TIOBE編程語言排名前100的榜單上,排在第93位。最近,TypeScript在TIOBE索引中排名第44位。
TypeScript已經成爲開發Web應用程序的一個強大的環境,在與標準JavaScript保持一致的同時,提供了其他顯著的改進。在本文中,我們將深入探討使用TypeScript、Node.js和TypeORM創建一個強大的解決方案來管理服務器端的數據庫訪問。我們將構建一個示例CRUD應用程序,作爲一個完整的端到端解決方案。我們假定讀者都很熟悉JavaScript。本文將包含以下幾個小節。
- 搭建環境
- 創建項目
- 配置項目
- 創建實體
- 創建數據庫連接,生成數據表
- 運行應用程序
- 查看數據表
- 使用Repository添加數據
- 使用連接管理器查找實體
- 使用Repository查找實體
- 更新數據
- 移除數據
- 創建一對一的關係
- 通過關係查找對象
- 創建一對多的關係
- 創建多對多的關係
搭建環境
下載和安裝相關軟件。
接下來,我們需要安裝一些Node.js模塊(包)。我們需要使用TypeORM包,這個包爲TypeScript提供了對象到關係的映射,用來訪問包括MySQL在內的大多數關係型數據庫。
安裝typeorm包。
npm install typeorm -g
安裝reflect-metadata庫,因爲在使用類Decorator時需要使用它。跟Decorator一樣,reflect-metadata庫也是實驗性的。
npm install reflect-metadata -g
安裝Node類型。
npm install @types/node -g
安裝MySQL數據庫驅動程序。
npm install mysql -g
創建項目
創建一個用於MySQL數據庫的TypeORM項目,項目名稱隨意(比如MySQLProject)。
typeorm init --name MySQLProject --database mysql
項目創建好後會生成一個MySQLProject目錄,進入這個目錄,可以列出項目文件。
cd MySQLProject
C:\Typescript\MySQLProject>DIR
Volume in drive C is OS
Volume Serial Number is BEFC-C04A
Directory of C:\Typescript\MySQLProject
02/02/2019 05:17 PM <DIR> .
02/02/2019 05:17 PM <DIR> ..
02/02/2019 05:13 PM 47 .gitignore
02/02/2019 05:17 PM <DIR> node_modules
02/02/2019 05:13 PM 473 ormconfig.json
02/02/2019 05:17 PM 46,178 package-lock.json
02/02/2019 05:17 PM 406 package.json
02/02/2019 05:13 PM 172 README.md
02/02/2019 05:13 PM <DIR> src
02/02/2019 05:13 PM 298 tsconfig.json
6 File(s) 47,574 bytes
4 Dir(s) 25,897,005,056 bytes free
C:\Typescript\MySQLProject>
安裝項目依賴項。
npm install
命令輸出如下所示:
C:\Typescript>typeorm init --name MySQLProject --database mysql
Project created inside C:\Typescript/MySQLProject directory.
C:\Typescript>cd MySQLProject
C:\Typescript\MySQLProject>npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No repository field.
npm WARN [email protected] No license field.
added 153 packages from 447 contributors and audited 231 packages in 70.022s
found 0 vulnerabilities
MySQLProject的配置和源碼可以從GitHub上下載,其中還包括本文將要添加的腳本文件。
配置項目
TypeScript的編譯器選項配置在tsconfig.json中。修改tsconfig.json,啓用下面的特性:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
其他的編譯器選項,比如目標編譯版本,已經在tsconfig.json預先配置好了。–experimentalDecorators選項用於啓用Decorator特性。–emitDecoratorMetadata選項用於爲被裝飾的聲明觸發類型元數據。這裏需要導入reflect-metadata庫,用來觸發裝飾器元數據。
修改ormconfig.json的數據庫配置,以便連接到MySQL數據庫。如果使用的是本地數據庫,就使用默認的host和port,username和password填寫實際的用戶名和密碼即可。
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "mysql",
"database": "mysql",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}
創建實體
在這一小節,我們將創建一個實體,作爲日誌目錄應用程序的模型。一個實體就是一個類,映射到一個數據表。使用typeorm庫提供的@Entity()就可以將一個類聲明爲一個實體。在entity目錄中添加一個Catalog.ts文件來定義一個實體。在這個文件中使用import語句來導入typeorm庫提供的Entity、Column和PrimaryGenerationColumn函數。
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
導出一個叫作Catalog的類,並使用@Entity()註解它。
@Entity()
export class Catalog {
…
}
完整的Catalog.ts文件可在GitHub上查看。一個基本的實體由多個列組成,並且每個實體必須指定至少一個主列。TypeORM提供了幾種主列,如表1所示。
主列類型 | 描述 |
---|---|
@PrimaryColumn() | 創建一個主列。列的類型是可選的,如果沒有指定,則從屬性類型進行推斷。主列的值必須由用戶提供。 |
@PrimaryGeneratedColumn() | 創建一個主列,類型爲int,值是自增的。 |
@PrimaryGeneratedColumn(“uuid”) | 創建一個主列,值爲自動生成的唯一uuid。 |
在Catalog實體中增加一個叫作id的主列。MySQL數據庫支持自增的主列,會爲每個新數據行自動分配一個唯一的主鍵值。要啓用這個特性,請使用@PrimaryGeneratedColumn。
@PrimaryGeneratedColumn()
id: number;
然後加入其他列:journal、publisher、edition、title和author,都是string類型。再增加一個叫作isPublished的列,類型爲Boolean,用來表示條目是否已發佈。實體的屬性類型被適當地映射成數據庫列的類型,具體因不同的數據庫而異。string類型會被映射成varchar(255),或者其他類似的數據庫類型。number類型會被映射成integer,或者其他類似的數據庫類型。實體到數據庫的映射也可以由用戶指定。例如,將string類型的title列映射到MySQL數據庫的text類型。
@Column("text")
title: string;
string類型的默認長度是255,但也可以指定自定義長度:
@Column({
length: 25
})
edition: string;
將下列代碼拷貝到實體文件中。
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class Catalog {
@PrimaryGeneratedColumn()
id: number;
@Column()
journal: string;
@Column()
publisher: string;
@Column({
length: 25
})
edition: string;
@Column("text")
title: string;
@Column()
author: string;
@Column()
isPublished: boolean;
}
創建數據庫連接,生成數據表
因爲我們要開發的是一個CRUD的應用程序,所以需要連接到MySQL數據庫。
在這個小節,我們需要編寫一個TypeScript腳本來連接MySQL數據庫,併爲entity目錄中的實體創建數據表。我們在entity/Catalog.ts定義了一個實體,請把這個目錄中的其他文件刪除,包括項目默認生成的User.ts。在MySQLProject項目的src目錄中創建一個index.ts文件,並導入reflect-metadata庫。從typeorm庫中導入createConnection函數,然後導入entity目錄中的Catalog類。在這裏我們應該使用ES2017提供的async/await語法。await關鍵字將會掛起腳本的執行,直到promise得到滿足。使用createConnection函數創建連接,連接選項包括type、host、port、username、password、database和entities。
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "mysql",
database: "mysql",
entities: [
__dirname + "/entity/*.ts"
],
synchronize: true,
logging: false
}).then(async connection => {
…
…
}).catch(error => console.log(error));
在then代碼塊中創建一個Catalog實體。
let catalog = new Catalog();
設置實體的屬性。
catalog.journal = "Oracle Magazine";
catalog.publisher = "Oracle Publishing";
catalog.edition = "March-April 2005";
catalog.title = "Starting with Oracle ADF";
catalog.author = "Steve Muench";
catalog.isPublished = true;
獲取EntityManager實例,調用save方法來保存實體。
await connection.manager.save(catalog);
save方法將所有給定的實體保存到數據庫中。這個方法首先會驗證實體是否已經存在數據庫中,如果存在,它將會更新這個實體,如果不存在,就將實體保存到數據庫中。用類似的方法添加另外一個實體。src/index.ts的代碼如下:
import "reflect-metadata";
import {createConnection} from "typeorm";
import {Catalog} from "./entity/Catalog";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "mysql",
database: "mysql",
entities: [
__dirname + "/entity/*.ts"
],
synchronize: true,
logging: false
}).then(async connection => {
let catalog = new Catalog();
catalog.journal = "Oracle Magazine";
catalog.publisher = "Oracle Publishing";
catalog.edition = "March-April 2005";
catalog.title = "Starting with Oracle ADF";
catalog.author = "Steve Muench";
catalog.isPublished = true;
await connection.manager.save(catalog);
console.log('Catalog has been saved'+'\n');
let catalog2 = new Catalog();
catalog2.journal = "Oracle Magazine";
catalog2.publisher = "Oracle Publishing";
catalog2.edition = "November December 2013";
catalog2.title = "Engineering as a Service";
catalog2.author = "David A. Kelly";
catalog2.isPublished = true;
await connection.manager.save(catalog2);
console.log('Catalog has been saved'+'\n');
}).catch(error => console.log(error));
運行應用程序
使用下面的命令運行應用程序。
npm start
在運行index.ts時,它會連接到數據庫,並在數據庫中創建指定的實體。數據庫中只會有一個Catalog表。這個命令的輸出如下所示:
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
Catalog has been saved
Catalog has been saved
查看數據庫表
接下來,我們將通過MySQL CLI來查看生成的數據庫表。啓動MySQL CLI,使用mysql連接數據庫。
C:\mysql-5.7.25-winx64\mysql-5.7.25-winx64\bin>mysql -u root -p
Enter password: *****
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.7.25 MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
切換到mysql數據庫。
mysql> use mysql
Database changed
列出數據表,之前生成的catalog表也在其中。
mysql> SHOW TABLES;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| catalog |
…
| user |
+---------------------------+
32 rows in set (0.00 sec)
Describe the catalog table and it lis
使用desc命令,它將列出與實體Catalog字段對應的列。因爲我們使用了自動生成的自增長主鍵列,數據庫自動爲我們設置id列的值。
mysql> DESC catalog;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| journal | varchar(255) | NO | | NULL | |
| publisher | varchar(255) | NO | | NULL | |
| edition | varchar(25) | NO | | NULL | |
| title | text | NO | | NULL | |
| author | varchar(255) | NO | | NULL | |
| isPublished | tinyint(4) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)
運行SQL的SELECT語句獲取數據。
mysql> SELECT * FROM catalog;
| id | journal | publisher | edition | title
| author | isPublished |
| 1 | Oracle Magazine | Oracle Publishing | March-April 2005 | Starting w
ith Oracle ADF | Steve Muench | 1 |
| 2 | Oracle Magazine | Oracle Publishing | November December 2013 | Engineering as a Service | David A. Kelly | 1 |
2 rows in set (0.01 sec)
mysql>
使用Repository添加數據
我們可以通過兩種方式來操作實體,一個是EntityManager,我們已經用過了,還有一種是Repository,我們將在這一小節中使用它。如果項目中有很多實體,使用Repository更合適,因爲每種實體可以有自己的Repository。Repository提供了與EntityManager相似的功能。我們已經通過EntityManager往數據庫中添加了兩個實體。在這一小節中,我們將使用Repository添加第三個實體。我們將繼續使用index.ts,所以需要將之前使用EntityManager創建Catalog條目的代碼移除。然後,像之前那樣創建Catalog實體,並從數據庫中連接中獲取Repository實例。
let catalogRepository = connection.getRepository(Catalog);
使用save函數保存Catalog實例。
await catalogRepository.save(catalog);
index.ts的代碼如下:
import {createConnection} from "typeorm";
import {Catalog} from "./entity/Catalog";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "mysql",
database: "mysql",
entities: [
__dirname + "/entity/*.ts"
],
synchronize: true,
logging: false
}).then(async connection => {
let catalog = new Catalog();
catalog.journal = "Oracle Magazine";
catalog.publisher = "Oracle Publishing";
catalog.edition = "November December 2013";
catalog.title = "Quintessential and Collaborative";
catalog.author = "Tom Haunert";
catalog.isPublished = true;
let catalogRepository = connection.getRepository(Catalog);
await catalogRepository.save(catalog);
console.log('Catalog has been saved'+'\n');
let [all_Catalogs, CatalogsCount] = await catalogRepository.findAndCount();
console.log('Catalogs count: ', CatalogsCount+'\n');
}).catch(error => console.log(error));
像之前那樣運行應用程序,數據庫中就會出現第三個Catalog實體。因爲之前已經添加過兩個實體,所以現在數據庫中有三個實體。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
Catalog has been saved
Catalogs count: 3
運行SQL查詢,可以得到三行數據。
mysql> SELECT * FROM catalog;
| id | journal | publisher | edition | title
| author | isPublished |
| 1 | Oracle Magazine | Oracle Publishing | March-April 2005 | Starting w
ith Oracle ADF | Steve Muench | 1 |
| 2 | Oracle Magazine | Oracle Publishing | November December 2013 | Engineerin
g as a Service | David A. Kelly | 1 |
| 3 | Oracle Magazine | Oracle Publishing | November December 2013 | Quintessen
tial and Collaborative | Tom Haunert | 1 |
3 rows in set (0.00 sec)
mysql>
使用EntityManager查找實體
在這一小節,我們將使用EntityManager來查找數據。EntityManager提供了幾種方法用來查找數據,如表2所示。
方法 | 描述 |
---|---|
find | 根據提供的選項查找實體。 |
findAndCount | 根據提供的選項查找並計算實體的個數。分頁選項將被忽略。 |
findByIds | 根據提供的ID查找實體。 |
findOne | 查找符合選項的第一個實體。 |
findOneOrFail | 查找符合選項的第一個實體,如果沒有,則查找失敗。 |
修改index.ts,以便查找所有的Catalog實體。使用find方法查找所有Catalog實體。
createConnection({
...
...
}).then(async connection => {
let savedCatalogs = await connection.manager.find(Catalog);
console.log("All catalogs from the db: ", savedCatalogs);
}).catch(error => console.log(error));
運行應用程序,顯示所有的Catalog實體(在運行Repository之前只會有兩個實體):
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
All catalogs from the db: [ Catalog {
id: 1,
journal: 'Oracle Magazine',
publisher: 'Oracle Publishing',
edition: 'March-April 2005',
title: 'Starting with Oracle ADF',
author: 'Steve Muench',
isPublished: true },
Catalog {
id: 2,
journal: 'Oracle Magazine',
publisher: 'Oracle Publishing',
edition: 'November December 2013',
title: 'Engineering as a Service',
author: 'David A. Kelly',
isPublished: true } ]
使用Repository查找實體
在這一小節,我們將使用Repository來查找實體。不過我們需要先安裝class-transformer包,用於序列化和反序列化JSON對象。
C:\Typescript\MySQLProject>npm install class-transformer -g
+ [email protected]
added 1 package from 1 contributor in 10.452s
在index.ts中獲取Repository實例,然後是find方法查找實體。find方法的語法如下所示:
find(options?: FindManyOptions<Entity>)
FindManyOptioins可以包含表3中所示的一個或多個屬性。
屬性 | 描述 |
---|---|
cache | 啓用或禁用查詢結果緩存。 |
join | 指定需要加載的關係。 |
loadEagerRelations | 指定是否加載關係實體,默認情況下會加載。 |
loadRelationIds | 指定是否加載關係實體的ID。如果設置爲true,所有的關係實體ID都會被加載,並被映射成關係值。 |
order | 指定實體的排序順序。 |
relations | 指定要加載的實體關係。 |
select | 指定要查詢哪些列。 |
skip | 需要跳過的實體數量。 |
take | 查詢的實體的最大數量。 |
where | 指定查詢條件。 |
修改index.ts,使用find方法查找實體。首先,獲得一個Repository對象,然後使用find方法查找所有實體,並且select選項只指定title和author兩個列。
let allCatalogs = await catalogRepository.find({ select: ["title", "author"] });
使用serialize方法序列化JSON結果。
console.log("All Catalogs from the db: ", serialize(allCatalogs)+'\n');
使用findOne方法查找id爲1的實體,並使用serialize方法序列化JSON結果。
let firstCatalog = await catalogRepository.findOne(1);
console.log('First Catalog from the db: ', serialize(firstCatalog)+'\n');
接下來,使用findOne方法查找title爲”Engineering as a Service“的第一個實體。
let specificTitleCatalog = await catalogRepository.findOne({ title: "Engineering as a Service"});
輸出JSON結果。
console.log("'Engineering as a Service' Catalog from the db: ", serialize(specificTitleCatalog)+'\n');
查找所有edition爲”November December 2013“的實體。
let allSpecificEditionCatalogs = await catalogRepository.find({ edition: "November December 2013"});
console.log('All November December 2013 Catalogs: ', serialize(allSpecificEditionCatalogs)+'\n');
查找所有isPublished爲true的實體。
let allPublishedCatalogs = await catalogRepository.find({ isPublished: true });
console.log('All published Catalogs: ', serialize(allPublishedCatalogs)+'\n');
運行應用程序,輸出如下所示:
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
All Catalogs from the db: [{"title":"Starting with Oracle ADF","author":"Steve Muench"},{"title":"Engineering as a Service","author":"David A. Kelly"},{"title":"Quintessential and Collaborative","author":"Tom Haunert"}]
First Catalog from the db: {"id":1,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"March-April 2005","title":"Starting with Oracle ADF","author":"Steve Muench","isPublished":true}
'Engineering as a Service' Catalog from the db: {"id":2,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Engineering as a Service","author":"David A. Kelly","isPublished":true}
All November December 2013 Catalogs: [{"id":2,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Engineering as a Service","author":"David A. Kelly","isPublished":true},{"id":3,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Quintessential and Collaborative","author":"Tom Haunert","isPublished":true}]
All published Catalogs: [{"id":1,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"March-April 2005","title":"Starting with Oracle ADF","author":"Steve Muench","isPublished":true},{"id":2,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Engineering as a Service","author":"David A. Kelly","isPublished":true},{"id":3,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Quintessential and Collaborative","author":"Tom Haunert","isPublished":true}]
更新實體
在這一小節,我們將更新Catalog實體。EntityManager和Repository都提供了update方法用於更新實體。update方法速度很快很高效,但存在以下兩個限制:
- 更新操作不涉及級聯、關係或其他操作。
- 不驗證數據庫中是否存在要更新的實體。
save方法會更新已存在的實體,如果實體不存在則新增,所以save方法不存在update方法那樣的問題。我們將更新一個實體的標題字段,這個實體的id爲1,將標題從”Starting with Oracle ADF“更新爲”Beginning with Oracle ADF“。
查找id爲1的實體。
let catalogToUpdate = await catalogRepository.findOne(1);
將標題字段改爲”Beginning with Oracle ADF“。
catalogToUpdate.title = "Beginning with Oracle ADF";
使用save方法保存實體。
await catalogRepository.save(catalogToUpdate);
然後再次查找id爲1的實體。
let updatedCatalog = await catalogRepository.findOne(1);
輸出被更新過的結果。
console.log('Updated Catalog from the db: ', serialize(updatedCatalog)+'\n');
運行應用程序更新實體,並輸出更新過的實體。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
First Catalog from the db: {"id":1,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"March-April 2005","title":"Beginning with Oracle ADF","author":"Steve Muench","isPublished":true}
刪除實體
在這一小節,我們將移除一個實體。EntityManager和Repository都提供了兩個方法用於刪除實體,如表4所示。
方法 | 描述 |
---|---|
delete | 根據非空條件刪除實體。與update方法一樣,它不檢查要刪除的實體是否存在,而且不包括級聯和關係。不過這個方法速度也很快很高效。 |
remove | 從數據庫中移除一個實體。 |
修改index.ts代碼以便演示如何移除實體。
使用findOne方法查找id爲1的實體。
let catalogToRemove = await catalogRepository.findOne(1);
使用remove方法移除實體。
await catalogRepository.remove(catalogToRemove);
然後,查找被刪除的實體,看看是否已被刪除。如果被刪除,就不應該輸出這個實體。
let firstCatalog = await catalogRepository.findOne(1);
console.log("First Catalog from the db: ", serialize(firstCatalog));
運行應用程序,findOne的結果是undefined,說明實體已被刪除。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
First Catalog from the db: undefined
在MySQL CLI中運行SQL查詢,只有一行數據。
mysql> SELECT * FROM catalog;
| id | journal | publisher | edition | title
| author | isPublished |
| 2 | Oracle Magazine | Oracle Publishing | November December 2013 | Engineerin
g as a Service | David A. Kelly | 1 |
| 3 | Oracle Magazine | Oracle Publishing | November December 2013 | Quintessen
tial and Collaborative | Tom Haunert | 1 |
2 rows in set (0.00 sec)
mysql>
創建實體間的一對一關係
TypeORM支持幾種實體間關係:
- 一對一
- 一對多、多對一
- 多對多
TypeORM分別爲這些關係提供了相應的Decorator和函數,如表5所示。
函數 | 描述 |
---|---|
OneToOne | 指定實體間的一對一關係 |
JoinColumn | 指定一對一關係的所有方 |
OneToMany | 指定實體間的一對多關係 |
ManyToOne | 指定實體間的多對一關係 |
ManyToMany | 指定實體間的多對多關係 |
JoinTable | 指定多對多關係的所有方 |
在這一小節,我們將討論實體間的一對一關係。創建第二個實體CatalogTimestamp,在entity目錄創建CatalogTimestamp.ts,並導入額外的OneToOne和JoinColumn函數。
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {Catalog} from "./Catalog";
導出CatalogTimestamp類,並加上@Entity(),讓它成爲一個實體。
@Entity()
export class CatalogTimestamp {
…
}
聲明主鍵列ID。
@PrimaryGeneratedColumn()
id: number;
增加列firstAdd、firstUpdated和lastUpdated,所有都是string類型。
@Column()
firstAdded: string;
@Column()
firstUpdated: string;
@Column()
lastUpdated: string;
使用@OneToOne爲Catalog添加一對一關係。實體間關係可以是單向也可以是雙向的。我們需要指定雙向關係。type => Catalog(或者() => Catalog)將返回一對一關係所對應的實體類。catalog => catalog.timestamp返回關係的另一方。@JoinColumn()指定一對一關係的所有方,並且只有其中一方是所有方。
@OneToOne(type => Catalog, catalog => catalog.timestamp)
@JoinColumn()
catalog: Catalog;
CatalogTimestamp在GitHub上可以找到,可以直接將其拷貝到CatalogEntity.ts中。
我們需要修改Catalog實體,並指定它與CatalogTimestamp的一對一關係。從TypeORM中導入OneToOne和JoinColumn函數。然後導入CatalogTimestamp類。
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {CatalogTimestamp} from "./CatalogTimestamp";
Catalog實體的其餘部分與之前一樣,只是需要再聲明一下@OneToOne。 type => CatalogTimestamp指定要關聯的實體。timestamp => timestamp.catalog指定反向關係。cascade設置爲true,當其中一個實體被保存時,另一個對應的實體也會被保存。也就是說,如果保存了Catalog的實例,相關聯的CatalogTimestamp實例也會被保存。
@OneToOne(type => CatalogTimestamp, timestamp => timestamp.catalog,{
cascade: true,
})
timestamp: CatalogTimestamp;
接下來,我們需要修改index.ts,使用Catalog和CatalogTimestamp實體創建一對一關係。需要額外導入CatalogTimestamp類。
import {CatalogTimestamp} from "./entity/CatalogTimestamp";
就像之前那樣創建一個Catalog實體實例。
let catalog = new Catalog();
catalog.journal = "Oracle Magazine";
catalog.publisher = "Oracle Publishing";
catalog.edition = "March-April 2005";
catalog.title = "Starting with Oracle ADF";
catalog.author = "Steve Muench";
catalog.isPublished = true;
另外再創建一個CatalogTimestamp實體實例。
let timestamp = new CatalogTimestamp();
timestamp.firstAdded = "Apr-8-2014-7:06:16-PM-PDT";
timestamp.firstUpdated = "Apr-8-2014-7:06:20-PM-PDT";
timestamp.lastUpdated = "Apr-8-2014-7:06:20-PM-PDT";
關聯CatalogTimestamp和Catalog實體實例。
timestamp.catalog = catalog;
獲取Catalog的Repository。
let catalogRepository = connection.getRepository(Catalog);
使用save方法保存Catalog實體。
await catalogRepository.save(catalog);
console.log("Catalog has been saved");
修改過的Catalog.ts可在GitHub上找到。
運行應用程序,實體以及實體間的關係將會被保存到數據庫中。因爲指定了cascade,所以CatalogTimestamp實體也會被保存。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
Catalog has been saved
Timestamp is saved, and relation between timestamp and catalog is created in the database too
通過實體間關係來查找對象
在這一小節,我們將通過實體間的關係來查找對象。EntityManager或Repository提供的find*方法可用來根據關係查找對象。修改index.ts,獲取Repository實例。查找所有實體,包括它們之間的關係。FindManyOptions的relations屬性指定了與timestamp的關係。
let catalogs = await catalogRepository.find({relations: ["timestamp"]});
輸出JSON結果。
console.log(serialize(catalogs));
運行應用程序,輸出新增的Catalog實體和CatalogTImestamp實體。三個Catalog實體中只有一個擁有非空的timestamp。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
[{"id":6,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"March-April 2005","title":"Starting with Oracle ADF","author":"Steve Muench","isPublished":true,"timestamp":{"id":1,"firstAdded":"Apr-8-2014-7:06:16-PM-PDT","firstUpdated":"Apr-8-2014-7:06:20-PM-PDT","lastUpdated":"Apr-8-2014-7:06:20-PM-PDT"}},{"id":2,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Engineering as a Service","author":"David A. Kelly","isPublished":true,"timestamp":null},{"id":3,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"November December 2013","title":"Quintessential and Collaborative","author":"Tom Haunert","isPublished":true,"timest
amp":null}]
find*方法加上FindOneOptions和FindManyOptions適用於大部分查詢,而QueryBuilder適用於較複雜的查詢,因爲它提供了一些選項,如WHERE表達式、HAVING表達式、ORDER BY表達式、GROUP BY表達式、LIMIT表達式和OFFSET表達式,還有連接關係,如內連接和外連接、沒有選擇的連接、連接和映射功能、翻頁和子查詢。舉個例子,創建一個QueryBuilder,使用innerJoinAndSelect指定內連接,並使用getMany獲得多個結果。如果要獲取一個結果,可以使用getOne。
let catalogs = await connection
.getRepository(Catalog)
.createQueryBuilder("catalog")
.innerJoinAndSelect("catalog.timestamp", "timestamp")
.getMany();
console.log(serialize(catalogs));
運行應用程序,輸出Catalog和CatalogTimestamp。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
[{"id":6,"journal":"Oracle Magazine","publisher":"Oracle Publishing","edition":"March-April 2005","title":"Starting with Oracle ADF","author":"Steve Muench","isPublished":true,"timestamp":{"id":1,"firstAdded":"Apr-8-2014-7:06:16-PM-PDT","firstUpdated":"Apr-8-2014-7:06:20-PM-PDT","lastUpdated":"Apr-8-2014-7:06:20-PM-PDT
"}}]
列出mysql數據庫中的數據表,會看到catalog和catalog_timestamp。
mysql> use mysql
Database changed
mysql> show tables;
+---------------------------+
| Tables_in_mysql |
| catalog |
| catalog_timestamp |
在catalog_timestamp上運行DESC命令,除了其他列,還會列出catalogId外鍵。
mysql> DESC catalog_timestamp;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| firstAdded | varchar(255) | NO | | NULL | |
| firstUpdated | varchar(255) | NO | | NULL | |
| lastUpdated | varchar(255) | NO | | NULL | |
| catalogId | int(11) | YES | UNI | NULL | |
+--------------+--------------+------+-----+---------+----------------+
5 rows in set (0.02 sec)
在catalog_timestamp上運行SELECT查詢,查詢結果包括值爲6的外鍵列,它是catalog表的id,timestamp就是通過這個id與catalog進行關聯的。
mysql> SELECT * FROM catalog_timestamp;
| id | firstAdded | firstUpdated | lastUpdated
| catalogId |
| 1 | Apr-8-2014-7:06:16-PM-PDT | Apr-8-2014-7:06:20-PM-PDT | Apr-8-2014-7:06:2
0-PM-PDT | 6 |
1 row in set (0.00 sec)
在catalog上運行SELECT查詢,會列出值爲6的id。
mysql> SELECT * FROM catalog;
| id | journal | publisher | edition | title
| author | isPublished |
| 2 | Oracle Magazine | Oracle Publishing | November December 2013 | Engineerin
g as a Service | David A. Kelly | 1 |
| 3 | Oracle Magazine | Oracle Publishing | November December 2013 | Quintessen
tial and Collaborative | Tom Haunert | 1 |
| 6 | Oracle Magazine | Oracle Publishing | March-April 2005 | Starting w
ith Oracle ADF | Steve Muench | 1 |
3 rows in set (0.00 sec)
類似的,我們也可以創建多對多關係。接下來,我們將介紹一個一對多的例子。
創建一對多關係
爲了演示一對多關係,我們仍然需要兩個實體。在這一小節中,我們將使用CatalogEdition實體,它與CatalogEntry實體是一對多的關係。除了主鍵列id之外,CatalogEdition還指定了edition和isPublished列。@OneToMany定義了與CatalogEntry的一對多關係,包括反向關係,它們的關係是雙向的。
@OneToMany(type => CatalogEntry, catalogEntry => catalogEntry.catalogEdition)
catalogEntries: CatalogEntry[];
CatalogEdition實體已經在GitHub上列出。
除了主鍵列id之外,CatalogEntry還指定了title、author、isPublished。@ManyToOne指定了CatalogEdition的多對一關係,包括反向關係。
@ManyToOne(type => CatalogEdition, catalogEdition => catalogEdition.catalogEntries)
catalogEdition: CatalogEdition;
CatalogEntry實體也已經在GitHub上列出。
像下面這樣修改index.ts。
import "reflect-metadata";
import {createConnection} from "typeorm";
import {CatalogEdition} from "./entity/Edition";
import {CatalogEntry} from "./entity/Section";
import {serialize} from "class-transformer";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "mysql",
database: "mysql",
entities: [
__dirname + "/entity/*.ts"
],
synchronize: true,
logging: false
}).then(async connection => {
// Create entity instances and save data to entities
}).catch(error => console.log(error));
運行應用程序,創建表和它們之間的關係。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
列出mysql數據庫的表,可以看到catalog_edition和catalog_entry表。
mysql> show tables;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| catalog |
| catalog_edition |
| catalog_entry |
| catalog_timestamp |
對catalog_edition進行DESC,將列出id、edition和isPublished三個列。
mysql> DESCRIBE catalog_edition;
| Field | Type | Null | Key | Default | Extra |
| id | int(11) | NO | PRI | NULL | auto_increment |
| edition | varchar(255) | NO | | NULL | |
| isPublished | tinyint(4) | NO | | NULL | |
3 rows in set (0.01 sec)
對catalog_entry進行DESC,列出的內容將包括外鍵catalogEditionId。
mysql> DESCRIBE catalog_entry;
| Field | Type | Null | Key | Default | Extra |
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| author | varchar(255) | NO | | NULL | |
| isPublished | tinyint(4) | NO | | NULL | |
| catalogEditionId | int(11) | YES | MUL | NULL | |
5 rows in set (0.01 sec)
創建多對多關係
在這一小節,我們將演示創建實體之間的多對多關係,這次仍然需要兩個實體。我們將使用兩個新的實體Edition和Section。Section定義了id和name,它與Edition的多對多關係是通過@ManyToMany來定義的,如下所示。
@ManyToMany(type => Edition, edition => edition.sections)
editions: Edition[];
Section實體已經在GitHub上列出。
Edition實體也指定了id和name。@ManyToMany定義了它與Section間的關係,包括反向關係。@JoinTable()指定了關係的所有方。
@ManyToMany(type => Section, section => section.editions)
@JoinTable()
sections: Section[];
Edition實體已經在GitHub上列出。
修改index.ts,創建兩個Edition實例,並保存到數據庫中。
let edition1 = new Edition();
edition1.name = "January February 2019";
await connection.manager.save(edition1);
let edition2 = new Edition();
edition2.name = "November December 2018";
await connection.manager.save(edition2);
創建一個Section實例。
let section = new Section();
section.name = "Application Development";
將editions關係設置爲edition1和edition2。
section.editions = [edition1, edition2];
將Section實體保存到數據庫。
await connection.manager.save(section);
創建另一個Section實例,將editions設置爲edition1,並保存。
let section2 = new Section();
section2.name = "DBA";
section2.editions = [edition1];
await connection.manager.save(section2);
創建第三個Section實例,使用關係edition1,並保存到數據庫。使用findOne方法查找一個Section實例,包括與editions關係相關聯的Edition。
const loadedSection = await connection
.getRepository(Section)
.findOne(1, { relations: ["editions"] });
輸出JSON結果。
console.log(serialize(loadedSection));
修改過的index.ts已經在GitHub上列出。
運行應用程序,創建Edition和Section之間的多對多關係。
C:\Typescript\MySQLProject>npm start
> [email protected] start C:\Typescript\MySQLProject
> ts-node src/index.ts
{"id":1,"name":"Application Development","editions":[{"id":2,"name":"January February 2019"},{"id":3,"name":"November December 2018"}]}
列出edition和section表,還可以看到連接表edition_sections_section。
mysql> show tables;
| Tables_in_mysql |
| edition |
| edition_sections_section |
| section |
總結
TypeScript是JavaScript的超集,提供了面向對象的特性,比如接口和靜態類型、類型註解和類型推斷。TypeScript還提供了一些實驗性的特性,叫作Decorator,用於給類和類成員添加註解,實現額外的特性。TypeScript是爲現代大規模應用程序而設計的。在這篇文章中,我們介紹瞭如何使用TypeScript操作MySQL數據庫。TypeORM庫用來連接數據庫、創建和保存實體、創建實體間關係、查找實體、更新和刪除實體。我們還演示了使用TypeScript構建一個端到端的對象關係映射應用程序。
關於作者
Deepak Vohra是經過Sun認證的Java程序員和Web組件開發人員。Deepak在WebLogic Developer’s Journal、XML Journal、ONJava、Java .net、IBM developerWorks、Java Developer’s Journal、Oracle雜誌和devx上發表過Java和Java EE相關的技術文章。Deepak還出版了一本關於異步JavaScript和XML的書。