InfoQ ホームページ アーティクル MySQLデータベースと共にTypeScriptを使用
MySQLデータベースと共にTypeScriptを使用
2019年7月10日 読了時間 73 分
作者:
翻訳者
JavaScriptは、Web用に設計されたECMAScript仕様に基づくスクリプト言語です。JavaScriptは、クライアントサイドのみのスクリプト言語からクライアントサイドとサーバサイドの両方で動作するものへと進化しました。サーバサイドJavaScriptの最も注目すべき実装はNode.jsです。
問題
JavaScriptには、現代の大規模Webアプリケーションで使用される機能がいくつか欠けています。 型アノテーション、コンパイル時の型チェック、型推論などの機能です。JavaScriptコードは大規模アプリケーションでは複雑になります。
解決策
関連スポンサーコンテンツ
TypeScriptは、大規模アプリケーションにおけるJavaScriptの制限を緩和するために、JavaScriptの型付けされたスーパーセットとして導入されました。
TypeScriptは、JavaScriptの厳密な構文上のスーパーセットで、コンパイル時の型チェック、型アノテーション、型推論、型消去などの機能と、インターフェイスのサポートなどのオブジェクト指向の機能を追加したものです。TypeScriptはオープンソースのスクリプト言語であり、JavaScriptに変換されます。出力された出力は、マシンだけが読み取ることができるものではなく、クリーンで慣用的なJSです。
TypeScriptは、アノテーションとメタプログラミング構文を使ってクラスとクラスメンバに追加機能を追加するためのサポートを提供するDecoratorsと呼ばれる実験的な機能を追加しました。デコレータは@expression形式の宣言で、expressionは実行時に呼び出される関数として評価され、デコレートされた宣言に関する情報も含まれます。デコレータは、クラス宣言、メソッド、アクセッサ、プロパティ、アノテーションに注釈を付けるために使用されます。この記事ではデコレータが使用されます。
2012年に導入されたTypeScriptは最近人気が急上昇しています。最近のJavaScriptとWeb開発のInfoQのトレンドレポートには、「TypeScriptが劇的に増加し、GitHubで最も人気のあるプログラミング言語のトップ10に入っている」ことが記されています。2018年6月、TypeScriptは93位でプログラミング言語のTIOBEインデックスのトップ100でデビューし、翌月はトップ50にランクインしました。最近では、TypeScriptはTIOBEインデックスの44位にランクインしています。
TypeScriptはWebアプリケーションを作成するための強力な環境として浮上してきました。言語との整合性を保ちながら、標準のJavaScriptよりも大幅に改善されています。この記事では、Node.js、MySQL、TypeORMでTypeScriptを使用してサーバサイドTypeScriptでデータベースアクセスを管理するための強力なソリューションを作成するために必要な詳細について詳しく説明します。完全なエンドツーエンドのソリューションを提供するために、CRUDアプリケーションの例を作成します。 サンプルアプリケーションはジャーナルカタログをモデル化するものとします。JavaScriptをよく知っていることを前提としています。この記事には以下のセクションがあります。
- 環境の設定
- プロジェクトの作成
- プロジェクトの設定
- エンティティの作成
- テーブルを生成するためのコネクションの作成
- アプリケーションの実行
- データベーステーブルの探索
- リポジトリを使用したデータの追加
- Connection Managerを使用したエンティティの検索/li>
- リポジトリを使用したエンティティの検索
- データの更新
- データの削除
- 1対1のリレーションの作成
- リレーションを持つオブジェクトの検索
- 1対多リレーションの作成
- 多対多リレーションの作成
環境の設定
次のソフトウェアをダウンロードしてインストールします。
次に、Node.jsモジュール(パッケージ)をいくつかインストールする必要があります。TypeORM パッケージを使用します。これはMySQLデータベースを含むほとんどのリレーショナルデータベースにアクセスするためのTypeScriptのオブジェクトリレーショナルマッピングを提供します。
typeormパッケージをインストールしてください。
npm install typeorm -g
Install the reflect-metadata library, which is needed when using class decorators. The reflect-metadata library is experimental just as decorators are.
クラスデコレータを使用する場合に必要なreflect-metadataライブラリをインストールします。reflect-metadataライブラリはデコレータと同じように実験的なものです。
npm install reflect-metadata -g
ノードタイピングをインストールします。
npm install @types/node -g
MySQLデータベースドライバをインストールします。
npm install mysql -g
プロジェクトの作成
MySQLデータベース用のTypeORMプロジェクトを作成します。プロジェクト名は任意です。(MySQLProject)。
typeorm init --name MySQLProject --database mysql
プロジェクトディレクトリMySQLProjectが作成されます。ディレクトリ(cd)を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 MySQLProject@0.0.1 No repository field.
npm WARN MySQLProject@0.0.1 No license field.
added 153 packages from 447 contributors and audited 231 packages in 70.022s
found 0 vulnerabilities
MySQLProjectの設定ファイルとsrcファイルは、GitHubで入手できます。この記事で追加したスクリプトも含まれています。
プロジェクトの設定
TypeScript コンパイラオプションは、tsconfig.jsonで設定されています。 tsconfig.json を変更して、次の設定を有効にします。
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
ターゲットコンパイルバージョンなど、その他のコンパイラオプションはtsconfig.jsonに事前定義されています。--experimentalDecoratorsオプションを使用すると、JavaScript向けのECMAScript(ES)仕様標準に記載されているようにデコレータを実験的にサポートできます。--emitDecoratorMetadata設定は、ソース内のデコレートされた宣言向けのデザインタイプメタデータを生成します。デコレータメタデータを生成するには、reflect-metadataライブラリをTypeScriptプログラムにインポートする必要があります。
MySQLデータベースに接続するために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ファイルを追加してエンティティを定義します。 typeormライブラリからEntity, Column,およびPrimaryGeneratedColumn関数をインポートするためのimport 宣言を追加します。
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
Catalogというコードをエクスポートし、そのクラスを@Entity()でアノテーションまたはデコレートします。
@Entity()
export class Catalog {
...
}
完全なCatalog.tsスクリプトはGitHubから入手できます。基本エンティティは列で構成されており、すべてのエンティティは少なくとも1つのプライマリ列を宣言する必要があります。TypeORMは、表1で説明したように、いくつかの種類のプライマリ列を提供します。
プライマリ列種別
説明
@PrimaryColumn()
プライマリ列を作成する。列の種類はオプションで指定でき、設定されていない場合はプロパティタイプから推測される。プライマリ列の値はユーザ指定でなければならない。
@PrimaryGeneratedColumn()
自動インクリメント値で自動的に生成されるint型のプライマリカラムを作成する。
@PrimaryGeneratedColumn("uuid")
uuid値で自動的に生成されるプライマリカラムを作成する。 uuidは一意の文字列値である。
Table 1. プライマリ列種別
Catalogエンティティにidという名前のプライマリ列を追加します。MySQLデータベースでは、新しいデータ行ごとにデータベースによって一意の主キー値が自動的に割り当てられるプライマリ列の自動インクリメントがサポートされています。自動インクリメント機能を利用するには、自動生成された列@PrimaryGeneratedColumnを使用してください。
@PrimaryGeneratedColumn()
id: number;
サンプルアプリケーションでジャーナルカタログをモデル化しています。他の列を追加してください。journal, publisher, edition, titleおよびauthor ですべてstring型です。カタログエントリが公開されているかどうかを示すisPublished というタイプのBooleanを追加します。エンティティプロパティタイプは適切なデータベース列タイプにマッピングされます。これは使用するデータベースによって異なります。string型はvarchar (255)または類似のデータベースタイプにマッピングされます。 numberプロパティタイプは、integerまたは同様のデータベースタイプにマッピングされます。 entity-データベースタイプのマッピングもユーザが指定できます。例として、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データベースに接続する必要があります。
このセクションでは、MySQLデータベースに接続し、entityディレクトリに定義されたエンティティのテーブルを作成するためのTypeScriptスクリプトを開発します。entity/Catalog.tsファイルに定義したエンティティは1つだけです。プロジェクトのデフォルトのUser.tsを含む他のentityスクリプトをエンティティディレクトリから削除します。TypeScriptプロジェクトのMySQLProjectのsrcディレクトリにindex.jsを作成します。デコレータを使用できるようにするには、reflect-metadataライブラリをインポートします。typeormライブラリからcreateConnection関数をインポートします。entityディレクトリからCatalogクラスをインポートします。ES2017で追加されたasync/await構文を使用します。async/await構文では、非同期関数の前にasyncキーワードが付きます。awaitキーワードは、非同期関数のreturnが約束されるまでスクリプトの実行を中断します。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メソッドは、指定されたすべてのエンティティをデータベースに保存します。 saveメソッドは、まずエンティティがすでにデータベースに存在するかどうかを確認します。存在すればsaveメソッドによりエンティティが更新され、そうでない場合、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.jsを実行してデータベースに接続すると、プロジェクト内のエンティティのデータベーステーブルが作成されます。1つのデータベーステーブルCatalogのみが作成されます。テーブルデータが追加されます。コマンドからの出力は次のとおりです。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 start C:\Typescript\MySQLProject
> ts-node src/index.ts
Catalog has been saved
Catalog has been saved
データベーステーブルの調査
次に、MySQL CLI(コマンドラインインターフェース)から生成されたMySQLデータベーステーブルを調べます。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
テーブルをリスト表示すると、カタログテーブルが表示されます。
mysql> SHOW TABLES;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| catalog |
...
| user |
+---------------------------+
32 rows in set (0.00 sec)
catalogテーブルを説明します。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)
SELECTステートメントを使用して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 | Engineering as a Service | David A. Kelly | 1 |
2 rows in set (0.01 sec)
mysql>
リポジトリを使用したデータの追加
エンティティに対して操作を実行するには、既に使用したEntityManagerと、このセクションで説明するRepositoryの2つのオプションがあります。多数のエンティティを使用する場合は、各エンティティが独自のリポジトリに関連付けられているため、Repository を使用することをお勧めします。Repositoryには、EntityManagerとほとんど同じ機能があります。EntityManagerを使用してCatalogのインスタンスを2つ追加しました。このセクションでは、Repositoryを使用して3番目のCatalogインスタンスを追加します。 同じindex.jsを使用しますので、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インスタンスが保存されます。 以前に2つのカタログエントリを追加したため、カタログ数3が表示されます。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 start C:\Typescript\MySQLProject
> ts-node src/index.ts
Catalog has been saved
Catalogs count: 3
SQLクエリを実行すると、3行のデータが表示されます。
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>
Connection Managerを使用したエンティティの検索
このセクションでは、EntityManagerクラスを使ってデータを見つけます。EntityManagerには、表2で説明したようにデータを検索するためのいくつかの方法があります。
メソッド
説明
find
指定されたオプションでエンティティを検索する。
findAndCount
提供されたオプションでエンティティを検索してカウントする。ページネーションオプション(fromとtake)は無視される。
findByIds
指定されたIDのエンティティを検索する。
findOne
オプションに一致する最初のエンティティを見つける。
findOneOrFail
オプションに一致する最初のエンティティを検索し、なければ失敗する。
Table 2. EntityManagerメソッドを使ったデータ検索
すべてのCatalogエンティティを見つけるようにindex.tsを変更します。すべてのカタログエントリを検索するには、findメソッドを使用します。
createConnection({
...
...
}).then(async connection => {
let savedCatalogs = await connection.manager.find(Catalog);
console.log("All catalogs from the db: ", savedCatalogs);
}).catch(error => console.log(error));
アプリケーションを実行します。すべてのカタログエントリが表示されます(3番目のエンティティをリポジトリに追加する前にスクリプトが実行されると、2つのエンティティのみが表示されます)。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 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クラスを使います。しかし最初に、JSONオブジェクトをシリアライズ/デシリアライズするために使用されるclass-transformerパッケージをインストールする必要があります。
C:\Typescript\MySQLProject>npm install class-transformer -g
+ class-transformer@0.2.0
added 1 package from 1 contributor in 10.452s
index.tsでRepositoryのインスタンスを以前のように取得します。まず、エンティティを見つけるためにfindメソッドを使います。findメソッドの構文は以下のとおりです。
find(options?: FindManyOptions<Entity>)
FindManyOptionsは、表3で説明した1つ以上のプロパティとして指定されます。
プロパティ
説明
cache
クエリ結果のキャッシュを有効または無効にする。
join
リレーションをロードするかどうかを指定する。
loadEagerRelations
Eagerリレーションをロードするかどうかを指定する。デフォルトでは、Eagerリレーションはfindメソッドでロードされる。 「Eager」リレーションロードとは、エンティティがロードされたときに関連付けられたモデルのデータをロードすることである。
loadRelationIds
リレーションIDをロードするかどうかを指定する。 trueに設定すると、すべてのリレーションIDがロードされ、リレーション値にマッピングされる。
order
エンティティを並び順を指定する。
relations
エンティティのどのリレーションを読み込むかを指定する。
select
どの列を選択するかを指定する。
skip
スキップするエンティティまたはエンティティからオフセットするエンティティの数を指定する。
take
取得するエンティティの最大数である。
where
適用する条件を指定する。
Table 3. FindManyOptionsプロパティ
findメソッドを使用してエンティティを検索するように index.tsを変更します。まず、Repositoryオブジェクトを取得します。 次に、findメソッドを使用して、 selectオプションを使用してすべてのエンティティを検索し、title列とauthor列のみを選択します。
let allCatalogs = await catalogRepository.find({ select: ["title", "author"] });
結果をJSONとしてシリアル化するには、serializeメソッドを使用します。
console.log("All Catalogs from the db: ", serialize(allCatalogs)+'\n');
idが1のエンティティを検索するには、findOneメソッドを使用します。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が「2013年11月12日」であるすべてのエンティティを検索します。
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
> MySQLProject@0.0.1 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":"Ora
cle 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 Magaz
ine","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","publ
isher":"Oracle Publishing","edition":"November December 2013","title":"Engineeri
ng 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","isPubli
shed":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のCatalogエンティティのtitleを「Starting with Oracle ADF」から「Beginning with Oracle ADF」に更新します。CatalogエンティティのRepositoryのインスタンスを取得します。
idが1のエンティティを探します。
let catalogToUpdate = await catalogRepository.findOne(1);
titleを「Beginning with Oracle ADF」に設定します。
catalogToUpdate.title = "Beginning with Oracle ADF";
saveメソッドを使用してエンティティを保存します。
await catalogRepository.save(catalogToUpdate);
その後、idが1のエンティティをもう一度検索します。
let updatedCatalog = await catalogRepository.findOne(1);
更新されたエンティティをJSONとして出力します。
console.log('Updated Catalog from the db: ', serialize(updatedCatalog)+'\n');
アプリケーションを実行すると、エンティティが更新されます。更新されたエンティティが出力されます。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 start C:\Typescript\MySQLProject
> ts-node src/index.ts
First Catalog from the db: {"id":1,"journal":"Oracle Magazine","publisher":"Ora
cle Publishing","edition":"March-April 2005","title":"Beginning with Oracle ADF"
,"author":"Steve Muench","isPublished":true}
エンティティの削除
このセクションでは、エンティティを削除します。EntityManagerクラスとRepositoryクラスの両方に、エンティティを削除するための表4で説明されているメソッドが用意されています。
メソッド
説明
delete
条件を使用してエンティティを削除する。条件は空にできない。更新メソッドと同じ欠点がある。エンティティが存在するかどうかを確認しないことと、カスケードと関係が含まない。その前提を許容できれば、削除は速くて効率的である。
remove
データベースからエンティティを削除する。
Table 4. エンティティを削除するためのメソッド
index.tsを変更してエンティティの削除を実演します。CatalogエンティティのためのRepositoryのインスタンスを以前と同様に取得します。
findOneメソッドを使用して、idが1のエンティティを検索します。
let catalogToRemove = await catalogRepository.findOne(1);
remove メソッドを使用してエンティティを削除します。
await catalogRepository.remove(catalogToRemove);
その後、 findを実行して同じエンティティを出力し、削除されたかどうかを確認します。 削除されている場合、結果にエンティティが表示されません。
let firstCatalog = await catalogRepository.findOne(1);
console.log("First Catalog from the db: ", serialize(firstCatalog));
アプリケーションを実行すると、findOneの結果はundefinedです。これはエンティティが削除されたことを意味します。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 start C:\Typescript\MySQLProject
> ts-node src/index.ts
First Catalog from the db: undefined
MySQL CLIでSQLクエリを実行すると、idが1のデータ行が表示されていません。
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はエンティティ間のいくつかの種類の関係をサポートします。
- 1対1
- 1対多、多対1
- 多対多
表5で説明されているように、これらのそれぞれに対してデコレータと関数がtypeormで提供されています。
関数
説明
OneToOne
エンティティ間の1対1の関係を指定する
JoinColumn
1対1リレーションの所有側を指定する
OneToMany
エンティティ間の1対多の関係を指定する
ManyToOne
エンティティ間の多対1の関係を指定する
ManyToMany
エンティティ間の多対多の関係を指定する
JoinTable
多対多関係の所有側を指定する
Table 5. リレーションのための関数
このセクションでは、2つのエンティティ間の1対1の関係について説明します。Catalogのタイムスタンプ用で2番目のエンティティ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;
firstAdded、firstUpdatedとlastUpdated列を追加し、すべてstring型とします。
@Column()
firstAdded: string;
@Column()
firstUpdated: string;
@Column()
lastUpdated: string;
@OneToOneデコレータを使用して、Catalogエンティティと1対1の関係を追加します。リレーションは、単方向または双方向です。双方向リレーションを指定します。type => Catalogは、あるいは() => Catalogと指定することもできますが、1対1のリレーションが存在するエンティティのクラスを返します。関数catalog => catalog.timestampはリレーションの逆側を返します。@JoinColumn()デコレータは、1対1リレーションの所有側を指定します。 一方の側だけが所有側になることができます。
@OneToOne(type => Catalog, catalog => catalog.timestamp)
@JoinColumn()
catalog: Catalog;
CatalogTimestampエンティティは、GitHubに表示されます。リストをCatalogEntity.tsスクリプトにコピーします。
CatalogTimestampとの1対1のリレーションを指定するためにCatalogエンティティを変更する必要があります。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;
次に、CatalogエンティティとCatalogTimestampエンティティを使用して1対1の関係を作成するようにindex.tsを変更します。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のエンティティリポジトリを取得します。
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
> MySQLProject@0.0.1 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を変更します。CatalogのRepositoryインスタンスを取得します。リレーションを含むすべてのエンティティを見つけます。findメソッドのFindManyOptionsオプションのrelationsプロパティは、timestampリレーションを指定します。
let catalogs = await catalogRepository.find({relations: ["timestamp"]});
結果をJSONとして出力します。
console.log(serialize(catalogs));
アプリケーションを実行して、新しく追加されたCatalogエンティティと、関連付けられているCatalogTimestampエンティティを出力します。 3つのCatalogエンティティのうち1つだけが関連付けられたnullでないtimestampを持ちます。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 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","is
Published":true,"timestamp":{"id":1,"firstAdded":"Apr-8-2014-7:06:16-PM-PDT","fi
rstUpdated":"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":"Qui
ntessential and Collaborative","author":"Tom Haunert","isPublished":true,"timest
amp":null}]
FindOneOptionsおよびFindManyOptionsとともにfind*メソッドを使用する場合、ほとんどのクエリに適していますが、QueryBuilderの方が、WHERE句、HAVING句、ORDER BY句、GROUP BY句、LIMIT句、OFFSET句などを含む検索でエンティティのサブセットを特定するオプションや設定が増えるため、複雑なクエリに適しています。さらにリレーションの結合、内部結合および左結合、選択なしで結合 、結合とマッピング機能、ページネーション、サブクエリも指定できる。例として、リポジトリを取得し、QueryBuilderを作成します。innerJoinAndSelectでINNER JOINを指定します。複数の結果を取得するにはgetManyを使用してください。結果を1つだけ取得するには、getOneを使用する必要があります。
let catalogs = await connection
.getRepository(Catalog)
.createQueryBuilder("catalog")
.innerJoinAndSelect("catalog.timestamp", "timestamp")
.getMany();
console.log(serialize(catalogs));
CatalogTimestampと1対1の関係にあるCatalogエンティティを出力するようにアプリケーションを実行します。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 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","is
Published":true,"timestamp":{"id":1,"firstAdded":"Apr-8-2014-7:06:16-PM-PDT","fi
rstUpdated":"Apr-8-2014-7:06:20-PM-PDT","lastUpdated":"Apr-8-2014-7:06:20-PM-PDT
"}}]
MySQL CLIからmysqlデータベース内のMySQLテーブルを一覧表示し、catalogとcatalog_timestampテーブルを一覧表示します。
mysql> use mysql
Database changed
mysql> show tables;
+---------------------------+
| Tables_in_mysql |
| catalog |
| catalog_timestamp |
catalog_timestampテーブルを記述すると、他の列に加えて外部キー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ステートメントを実行すると、クエリ結果にtimestampが関連づけられているcatalogテーブル行の外部キー列 idの値6が含まれます。
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ステートメントを実行すると、idが6の行が表示されます。サンプルデータは異なる場合がありますが、外部キー値はtimestampに関連付けられています。
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)
同様に、1対多および多対多の関係を使用できます。次に、1対多の関係の例を説明します。
1対多リレーションの作成
1対多の使い方を説明するには、もう一度2つのエンティティが必要です。このセクションでは、CatalogEntryと1対多であるCatalogEditionエンティティを使用します。CatalogEditionエンティティは、プライマリ列idに加えて、editionとisPublishedの列を指定します。@OneToManyデコレータは、CatalogEntryと1対多の関係を、その逆の関係を含めて定義します。双方向リレーションが定義されます。
@OneToMany(type => CatalogEntry, catalogEntry => catalogEntry.catalogEdition)
catalogEntries: CatalogEntry[];
CatalogEditionエンティティは、GitHubプロジェクトに一覧化されています。
CatalogEntryエンティティは、プライマリ列idに加えて、title, author, isPublished列を指定します。@ManyToOneデコレータは、逆関係を含むCatalogEditionとの多対1の関係を指定します。
@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
> MySQLProject@0.0.1 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テーブルを表示すると、id、edition、isPublishedの3つの列が表示されます。
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テーブルを表示すると、外部キー列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)
多対多リレーションの作成
このセクションでは、エンティティ間に多対多の関係を作成するデモを示します。そのために2つのエンティティが必要になります。2つの新しいエンティティ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プロジェクトに一覧表示されています。
EditionとSectionの間の多対多の関係を示すために index.tsを変更します。Editionエンティティのインスタンスを2つ作成してデータベースに保存します。
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を1つのエディションedition1に設定します。Sectionエンティティインスタンスを保存します。
let section2 = new Section();
section2.name = "DBA";
section2.editions = [edition1];
await connection.manager.save(section2);
3番目のSectionエンティティインスタンスを作成し、edition1に関連付けてデータベースに保存します。次に、findOneメソッドを使用して、editionsリレーションによって関連付けられたEditionエンティティを含む1つのSectionエンティティを検索します。
const loadedSection = await connection
.getRepository(Section)
.findOne(1, { relations: ["editions"] });
結果をJSONとして出力します。
console.log(serialize(loadedSection));
修正されたindex.tsはGitHubプロジェクトに一覧表示されています。
アプリケーションを実行して、EditionとSectionの間に多対多の関係を作成します。id が1のSection エンティティおよび関連するEditionもJSONとして出力されます。
C:\Typescript\MySQLProject>npm start
> MySQLProject@0.0.1 start C:\Typescript\MySQLProject
> ts-node src/index.ts
{"id":1,"name":"Application Development","editions":[{"id":2,"name":"January Feb
ruary 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には、クラスをデコレート/アノテーションするためのDecoratorsという実験的な機能と、追加機能を提供するためのクラスメンバーが含まれています。TypeScriptは現代の大規模アプリケーション用に設計されています。この記事では、MySQLデータベースでTypeScriptを使用する方法について説明しました。TypeORMライブラリは、データベースとの接続、エンティティの作成と保存、エンティティ間の関係の作成、エンティティの検索、エンティティの更新と削除に使用されます。Node.js上で動作し、MySQLデータベースに接続して雑誌カタログをモデル化する、エンドツーエンドのオブジェクトリレーショナルマッピングアプリケーションでTypeScriptを使用する方法を示しました。
著者について
Deepak Vohra氏は、Sun認定のJavaプログラマーおよびSun認定のWebコンポーネント開発者です。Deepak氏は、JavaおよびJava EEに関連する技術記事をWebLogic Developer's Journal、XML Journal、ONJava、java.net、IBM developerWorks、Java Developer's Journal、Oracle Magazine、devxに公開しています。Deepak氏は、Asynchronous JavaScript and XMLについての本Ajax in Oracle JDeveloperも出版しています。
このコンテンツのトピックは Relational Databases です。
関連記事:
-
関連記事
トレンド
特集コンテンツ一覧
InfoQ ニュースレター
毎週火曜日に前週のまとめコンテンツをお送りいたします。(日本語版は不定期リリース)25万人のシニアな開発者コミュニティーにぜひご参加ください。 サンプルを見る