Edited at

Meteorのあんちょこ꒰。・ω・`;꒱ データベースアーキテクチャ

More than 1 year has passed since last update.


本流



参考


DB接続先


  • 127.0.0.1:3001 DB名=meteor(Robomongoでアクセスできた)


概要


  • サーバとクライアントと同じコードでDBにアクセスできる。

  • Meteorのクライアントはサーバデータのキャッシュを持っている。

  • クライアントでデータを変更すると、クライアントのキャッシュが更新され、即時にサーバのDBデータも更新される。(別のクライアントの変更が瞬時に伝わるため、共同編集が可能となる。)


DB操作


コレクションの生成(コンストラクタ)


new Meteor.Collection(name, [options])



  • DBのコレクション名をnameに指定すると、サーバ・クライアント間での同期が行われれるコレクションとなる。

  • DBのコレクション名をnameに指定しないと、サーバ・クライアント間での同期が行われないコレクションとなる。


コレクション内を検索


find


find(selector, options)



sample.js


var Employees = new Meteor.Collection('employees');

// IDで検索
Employees.find({ _id: '...' });

// 複数条件で検索(name='...'、かつ、age='...'の項目を検索)
Employees.find({ name: '...', age: '...' }); 
// ageの値が10以上
Employees.find({ age: { $gt: 10 } });
// nameの値が'しゅんぺい'以外のレコード
Employees.find({ name: { $not: 'しゅんぺい' } });

// 部分一致
const name = '太郎';
Employees.find({name : new RegExp(name, 'i')});

// 全検索
Employees.find({});



sort(結果をソートする)


name(昇順)、age(降順)で検索

Employees.find({}, {sort: [['name', 'asc'], ['age', 'desc']]});



skip(結果をスキップする件数)


5件スキップ

Employees.find({}, {skip: 5});



limit(最大の取得結果数)


5件のみ取得

Employees.find({}, {limit: 5});



fields(フィールド有効・無効)


  • 結果に含めるプロパティには1

  • 結果に含めないプロパティには0


// ageプロパティを除外する

Employees.find({}, {fields: {age: 0}})



reactive(結果をリアクティブ採否)


  • デフォルトはtrue(リアクティビティについては,後の連載記事をお待ちください)


forEach(先頭から逐次実行)


sample.js

var cursor = Employees.find();

cursor.forEach(function(employee) {
...
});


map


sample.js

// 従業員IDのみで構成される

var idList = cursor.map(function(employee) {
return employee._id;
});


fetch


count(結果総数)


rewind(カーソル初期化)


observe(カーソルの状態を監視)


sample.js

cursor.observe({

// 検索結果が増やされた
added: function(document, beforeIndex) {
console.log('added(追加された位置:' + beforeIndex + ')');
},
// 検索結果が変更された
changed: function(newDocument, atIndex, oldDocument) {
console.log('changed(位置:' + atIndex + ')');
},
// 検索結果内でオブジェクトのインデックスが変わった
moved: function(document, oldIndex, newIndex) {
console.log('moved(前の位置:' + oldIndex + ' 後の位置:' + newIndex + ')');
},
// 検索結果からオブジェクトが減った
removed: function(oldDocument, atIndex) {
console.log('removed(位置:' + atIndex + ')');
}
});


findOne(selector, options)


sample

var employee = Employees.findOne({_id: selected_employee_id});



insert(データ挿入)


sample.js

'click #addButton': function(e, template) {

// (5) コレクションに新たなデータを追加
Employees.insert({
name: nameElem.value,
age: ageElem.valueAsNumber
}, function(error, result) {
if (error) {
alert('エラーが発生しました');
}
});
},


update(データ更新)


  • update(selector, modifier, options, callback)


sample.js


// 年齢に40歳を指定する。
Employees.update({ _id: id }, { $set: { age: 40 }};

// ageプロパティの値を2増加させる。
Employees.update({ _id: id }, { $inc: { age: 2 }};

// コレクションの要素を更新する(完全入替)
Employees.update(
{ _id: selectedIdElem.value },
{
name: nameElem.value,
age: ageElem.valueAsNumber
}
);



  • 第三引数のoptions

  • multiというプロパティにtrueを設定すると、マッチしたドキュメント全てを更新する

  • falseを設定すると一件のみ更新する。デフォルトは{ multi: false }


  • 最後の引数callback


  • 処理が終了した際のコールバックを指定する。


  • サーバ上で実行する際には、コールバックを省略すると同期型のメソッドとなる。


  • 処理が終了するまでブロックします。それ以外の場合はブロックしない。



remove(データ更新)


  • remove(selector, callback)


  • 引数selectorに合致したドキュメントを全て削除する。


  • 最後の引数callbackには,処理が終了した際のコールバックを指定する。


  • サーバ上で実行する際には、コールバックを省略すると同期型のメソッドとなり、処理が終了するまでブロックする。それ以外の場合はブロックしない。


サンプルでは,削除ボタンをクリックされた際に,チェックされた行のレコードを全て削除するためにremoveメソッドを使用しています。$inセレクタを用いて,削除対象となるドキュメントを絞り込んでいます。


sample.js

// 削除ボタンをクリックされた際の処理

'click #removeButton': function(e, template) {
if (confirm('選択されているレコードを削除してもよろしいですか')) {
// チェックされているチェックボックスを全て取得
var selected = template.findAll('[name="selectedEmployees"]:checked');
// IDの配列に変換
var ids = selected.map(function(id) { return id.value; });
// コレクションの要素を削除する
Employees.remove({_id: { $in: ids }});
// 入力フォームをクリア
selectedIdElem.value = '';
nameElem.value = '';
ageElem.value = '';
}
},



サンプルコード


imports/api/employees.js

import { Meteor } from 'meteor/meteor';

import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';

export const Employees = new Meteor.Collection('employees');

Meteor.methods({
// 初期化
'employees.init'() {
// Employees.remove({});

// // 決め打ちでデータを登録する。
// var data = [
// { name: 'しゅんぺい', age: '34' },
// { name: 'たえこ', age: '33' },
// { name: 'こうたろう', age: '4' },
// { name: 'ちほ', age: '2' }
// ];
// // データをMongoDBに挿入する。
// data.forEach(function (emp) {
// Employees.insert(emp);
// })
},
// 追加
'employees.insert'(name, age) {
check(name, String);
check(age, String);
//console.log(name, age);
Employees.insert({name: name, age :age});
},
// 編集
'employees.update' (id, name, age) {
const employee = Employees.findOne(id);
console.log(employee)
console.log('employees.update', id, name, age);
Employees.update(id, { $set: { name: name, age: age } });

},
// 削除
'employees.remove'(employeeId) {
check(employeeId, String);
const employee = Employees.findOne(employeeId);
Employees.remove(employeeId);
},
});



  • 見かけ(UI)


imports/ui/employee.js

import { Meteor } from 'meteor/meteor';

import { Template } from 'meteor/templating';

import './employee.html';

Template.employee.events({
'click .edit'(event) {
const target = event.target;
const name = $(target).parents('tr').find('.name').val();
// this.nameではDBに登録された値が取得される。
const age = $(target).parents('tr').find('.age').val();
Meteor.call('employees.update', this._id, name, age);
},
'click .del'() {
if (confirm('削除しますか?')) {
Meteor.call('employees.remove', this._id);
}
},
});



imports/ui/employee.html

<template name="employee">

<tr>
<td class="id">{{_id}}</td>
<td><input type="text" class="name" value="{{name}}"/></td>
<td><input type="text" class="age" value="{{age}}" /></td>
<td><button class="edit">編集</button></td>
<td><button class="del">削除</button></td>
</tr>
</template>


  • body部


imports/ui/body.js

import { Meteor } from 'meteor/meteor';

import { Template } from 'meteor/templating';

import { Employees } from '../api/employees.js';

import './body.html';
import './employee.js';

Template.employees.helpers({
employees() {
return Employees.find();
},
});

Template.employees.events({
'click .add'(event) {

event.preventDefault();

const target = event.target;
const name = $(target).parents('tr').find('.name').val();
const age = $(target).parents('tr').find('.age').val();
// console.log(name, age);

Meteor.call('employees.insert', name, age);

$(target).parents('tr').find('.name').val('');
$(target).parents('tr').find('.age').val('');

// Meteor.call('employees.remove', this._id);
},
});



imports/ui/body.html

<body>

<h1>Database</h1>
{{> employees}}
</body>

<template name="employees">
<table id="employees">
<thead>
<th>System ID</th>
<th>名前</th>
<th>年齢</th>
<th>編集</th>
<th>削除</th>
</thead>
<tbody>
<tr>
<td>&nbsp;</td>
<td><input class="name" type="text" value="" placeholder="名前"></td>
<td><input class="age" type="text" value="" placeholder="年齢"></td>
<td>&nbsp;</td>
<td><button class="add">追加</button></td>
</tr>
{{#each employees}}
{{ > employee }}
{{/each}}
</tbody>
</table>
</template>



  • サーバメイン


server/main.js

import { Employees } from '../imports/api/employees.js';

Meteor.startup(() => {
Meteor.call('employees.init');
});



  • クライアントメイン


client/main.js

import '../imports/ui/body.js';



client/main.html

<head>

<title>database</title>
</head>


DBアクセス


console

$ meteor mongo -h

Options:
--url, -U return a Mongo database URL

$ meteor mongo -U
mongodb://127.0.0.1:3001/meteor

$ meteor mongo
MongoDB shell version: 2.6.7
connecting to: 127.0.0.1:3001/meteor
meteor:PRIMARY> db.employees.find()
{ "_id" : "jzqxufPYG3bDCCzfh", "name" : "しゅんぺい", "age" : "34" }
{ "_id" : "BEGgx8mJm93yqoGuu", "name" : "たえこ", "age" : "33" }
{ "_id" : "aB7PPJXsvzB3fYPuP", "name" : "こうたろう", "age" : "4" }
{ "_id" : "2KFbPkBrKsjLANog4", "name" : "ちほ", "age" : "2" }
meteor:PRIMARY> exit
bye

# mongodb標準のmongoクライアントツールからアクセスする
$ mongo 127.0.0.1:3001/meteor
MongoDB shell version: 3.2.8
connecting to: 127.0.0.1:3001/meteor
meteor:PRIMARY> show dbs
local 0.063GB
meteor 0.031GB
meteor:PRIMARY> exit
bye



  • ローカルで実行する際、DBは一律「127.0.0.1:3001/meteor」になるようだ。。。



セキュリティ向上


  • ここまでの設定の場合、DB全データがクライアントにキャッシュされる。

  • データが膨大になるなどの場合に対応できない。

  • 全クライアントから、全データにアクセスできてしまう。


console

$ meteor remove autopublish insecure

Changes to your project's package version selections:

autopublish removed from your project
insecure removed from your project

autopublish: removed dependency
insecure: removed dependency



  • クライアントサイドのキャッシュに全DBデータを展開すると膨大となる。


api/employees.js


export const Employees = new Meteor.Collection('employees');

if (Meteor.isServer) {
// 全データを公開する。
Meteor.publish('all-employees', function () {
return Employees.find();
});
}



  • autopublish解除により、展開できるデータをすぼめる。


ui/body.js

Template.body.onCreated(function bodyOnCreated() {

Meteor.subscribe('all-employees');
});


  • クライアントサイドでDBのCRUD操作が可能では危ない。

  • insecureを解除すると、クライアントから直接挿入はできなくなる。

  • Meteor.callによるサーバー関数の呼び出しによって実施する。


body.js

Template.employees.events({

'click .add button.add'(event) {

event.preventDefault();

const target = event.target;
const name = $(target).parents('tr').find('.name').val();
const age = $(target).parents('tr').find('.age').val();

if (name && age) {
// Employees.insert({name : name, age : age}, function () { alert('error'); });

// ↓直接、データ挿入せず、サーバ側の関数を経由する
Meteor.call('employees.insert', name, age);
$(target).parents('tr').find('.name').val('');
$(target).parents('tr').find('.age').val('');
}
},
});



バックアップ&復元


meteor mongo



バックアップ


console

$ mongodump -h 127.0.0.1 --port 3001 -d meteor



  • dumpフォルダが出来上がるので、それがバックアップファイル(jsonとbsonのデータ)となる


復元


  • meteorで空のDBを作成しておいて、そこに下記のコマンドを流すとリストアが正常終了した。

  • RobomongoでDB作成して、そのまま、下記のコマンドを流してもエラーとなった(詳細不明)。


console

$ mongorestore -h 127.0.0.1 --port 3001 -d meteor dump/meteor(path to dump)