参照リンク
背景
去年の中頃から金融系のトレードシステムのマイクロサービスの開発を担当しているのですが、予算や内部政治の関係でデータベースがOracle一択で、うまく動くORMが見つからなかったので作りました。
* 色々と探してみた結果、ちゃんとしてそうなモノだとTypeORMが唯一node.jsでOracle対応を唄っていたのですが、今年の2018年2月頃に検証した限りでは、ちゃんと動いていませんでした。
現行のプロジェクトではベータ版のORMを使っているのですが、その段階でいくつか欲しい機能と構造的に考慮が必要な部分もわかり、バージョン1を作るに当たって、ゼロから作り直しました。
開発ターゲットはOracle11gで行なっています(12cにすると11gに無いコマンドもあるので)。
今回はCRUD機能とベータ版のよく使う検索オプションを移行できたので、バージョン1として一つの括りとして公開しています。
使い方
使用例はgithub: Relax ORM TypeScriptデモに用意してあるので、ここでは各箇所考慮が必要だった点を載せて行こうと思います。
Entityの作り方
@Table('USER_TABLE')
export class User extends Entity<User>{
@PrimaryKey()
@Sequence('USER_SEQUENCE')
@Column('USER_ID')
id?: number;
@Column()
name?: string;
}
OracleではAutoIncrementが無いのでSequenceを定義できるようになっています。
レコードを作成するときに、これが定義してあるとその'.NEXTVAL'を自動で使うようになっています。
PrimaryKeyはUpdateする時のWHERE参照キーとなります。
データベースコネクションの作り方
const conn = new ConnectionManager({
user : DBCONFIG.user,
password : DBCONFIG.password,
connectString : DBCONFIG.connectString, // ex: localhost/xe
});
conn.addEntities([ User ]);
await conn.init();
シンプルにユーザ名、パスワードとコネクション名を入れます。
下の用にオプションとしてコネクションプールに関する設定も入れたりできます、インスタンス化の前であればoracledbのグローバルセッティングに関する設定も出来ます。
(この辺りは、ベータ版の反省を踏まえて)
ConnectionManager.config.maxRows = 100;
ConnectionManager.config.fetchAsString = [ oracledb.DATE, oracledb.NUMBER ];
const conn = new ConnectionManager({
user : DBCONFIG.user,
password : DBCONFIG.password,
connectString : DBCONFIG.connectString,
poolMax: 10,
poolMin: 40,
poolTimeout: 10,
});
検索と内部で発行されるクエリ
いくつかのシンプルなクエリの例です、上記で作成したエンティティに対して発行されます。全てPromiseで結果が帰っくるのでasync/awaitも簡単です。
RelaxORM - ReadMeにはupdateやdestroyの例もあります。
User.findAll()
// SELECT SEQ_NUM_USER, NAME FROM RLXORM.TB_USE
User.findAll( {where: { id: 1, name: 'walker' } });
// SELECT SEQ_NUM_USER, NAME FROM RLXORM.TB_USER WHERE SEQ_NUM_USER = :id$ AND NAME = :name$
User.findAll({
where: {
name: 'walker',
},
order: [
[ 'id',ResultOrder.ASC ],
[ 'name',ResultOrder.DESC ],
],
});
// SELECT SEQ_NUM_USER, NAME FROM RLXORM.TB_USER WHERE NAME = :name$ ORDER BY SEQ_NUM_USER ASC, NAME DESC
変わり種
オラクル(11g)には直接的なリミットやオフセットオプションが無く、順序などが絡んで来るとさらにややこしくなるのですが、そこでは、よく使われるイディオムを生成しています。
ページネーションなどの時に便利です。
User.findAll({
limit: 2,
})
/*
SELECT SEQ_NUM_USER, NAME FROM
( SELECT SEQ_NUM_USER, NAME, ROWNUM as TMP$ROWNUMBER FROM RLXORM.TB_USER )
WHERE TMP$ROWNUMBER <= :TMP$LIMIT
*/
User.findAll({
limit: 2,
order: [['name', ResultOrder.ASC]],
})
/*
SELECT SEQ_NUM_USER, NAME FROM
( SELECT SEQ_NUM_USER, NAME, ROW_NUMBER() OVER( ORDER BY NAME ASC ) as TMP$ROWNUMBER FROM RLXORM.TB_USER ORDER BY NAME ASC )
WHERE TMP$ROWNUMBER <= :TMP$LIMIT
*/
内部的な話
今のベータ版で一度起きたのですが、orderというプロパティがあり、バインド変数名':order' と精製されていました。他の名前だったら良かったのですが、ORDERはOracleの予約語ですのでエラーになっていたのですが、それを発見するまで結構時間を消費してしまいました。
なのでバインド用の変数名前を生成する時には直接使用せず、末尾に$を足したりしています。
npm/githubに公開
せっかく便利なものが出来たのでOSSとして広まれば良いなという単純な考えです。
あわよくば、みんながガンガン便利オプションを作りまくって超便利になればなぁ。とも考えています。
ガンガンPull Request待ってます。
後、使用した感想やバグレポ、改善要請なんかも嬉しいてす。
今後の展望
まだベータ版から移しきれていないIN, OR, LESS THAN, GRATER THAN, BETWEENなど機能があるので、近日中に完了します。
今始まっている新しいプロジェクトにも使っていく予定ですので、どんどん成熟したライブラリになっていく筈です。