Реляционное сопоставление объектов для fibjs
Установить
1npm install fib-orm
Тест
1npm run ci
Поддержка СУБД
- MySQL и МарияДБ
- SQLite
Функции
fib-orm добавляет набор методов синхронной версии для объекта node-orm.
Введение
Это модуль реляционного отображения объектов fibjs.
Пример:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37var orm = require("fib-orm");
var db = orm.connectSync("mysql://username:password@host/database");
var Person = db.define("person", {
name : String,
surname : String,
age : Number, // FLOAT
male : Boolean,
continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type
photo : Buffer, // BLOB/BINARY
data : Object // JSON encoded
}, {
methods: {
fullName: function () {
return this.name + ' ' + this.surname;
}
},
validations: {
age: orm.enforce.ranges.number(18, undefined, "under-age")
}
});
// add the table to the database
db.syncSync();
// add a row to the person table
Person.createSync({ id: 1, name: "John", surname: "Doe", age: 27 });
// query the person table by surname
var people = Person.findSync({ surname: "Doe" });
// SQL: "SELECT * FROM person WHERE surname = 'Doe'"
console.log("People found: %d", people.length);
console.log("First person: %s, age %d", people[0].fullName(), people[0].age);
people[0].age = 16;
people[0].saveSync();
Версия node.js выглядит следующим образом:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49var orm = require("orm");
orm.connect("mysql://username:password@host/database", function (err, db) {
if (err) throw err;
var Person = db.define("person", {
name : String,
surname : String,
age : Number, // FLOAT
male : Boolean,
continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type
photo : Buffer, // BLOB/BINARY
data : Object // JSON encoded
}, {
methods: {
fullName: function () {
return this.name + ' ' + this.surname;
}
},
validations: {
age: orm.enforce.ranges.number(18, undefined, "under-age")
}
});
// add the table to the database
db.sync(function(err) {
if (err) throw err;
// add a row to the person table
Person.create({ id: 1, name: "John", surname: "Doe", age: 27 }, function(err) {
if (err) throw err;
// query the person table by surname
Person.find({ surname: "Doe" }, function (err, people) {
// SQL: "SELECT * FROM person WHERE surname = 'Doe'"
if (err) throw err;
console.log("People found: %d", people.length);
console.log("First person: %s, age %d", people[0].fullName(), people[0].age);
people[0].age = 16;
people[0].save(function (err) {
// err.msg = "under-age";
});
});
});
});
});
Документация
Fibjs не добавил новых функций, при разработке документов можно обращаться к node-orm, нужно только изменить асинхронный вызов на синхронную версию wiki .
Настройки
Смотрите информацию в вики .
Подключение
Смотрите информацию в вики .
Модели
Модель – это абстракция одной или нескольких таблиц базы данных. Модели поддерживают ассоциации (подробнее ниже). Предполагается, что имя модели соответствует имени таблицы.
Модели поддерживают поведение для доступа к данным таблицы и управления ими.
Определение моделей
Смотрите информацию в вики .
Properties
Смотрите информацию в вики .
Instance Methods
Передаются во время определения модели.
1
2
3
4
5
6
7
8
9
10
11
12
13var Person = db.define('person', {
name : String,
surname : String
}, {
methods: {
fullName: function () {
return this.name + ' ' + this.surname;
}
}
});
var person = Person.getSync(4);
console.log( person.fullName() );
Model Methods
Определяются непосредственно на модели.
1
2
3
4
5
6
7
8
9var Person = db.define('person', {
name : String,
height : { type: 'integer' }
});
Person.tallerThan = function(height) {
return this.findSync({ height: orm.gt(height) });
};
var tallPeople = Person.tallerThan( 192);
Загрузка моделей [НЕ ПОДДЕРЖИВАЕТСЯ]
Модели могут находиться в отдельных модулях. Просто убедитесь, что модуль, содержащий модели, использует модуль Module.exports для публикации функции, которая принимает соединение с базой данных, а затем загружайте модели так, как вам нравится.
Примечание. Используя этот метод, вы можете иметь каскадные нагрузки.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// your main file (after connecting)
db.loadSync("./models");
// loaded!
var Person = db.models.person;
var Pet = db.models.pet;
// models.js
module.exports = function (db) {
db.loadSync("./models-extra");
db.define('person', {
name : String
});
};
// models-extra.js
module.exports = function (db) {
db.define('pet', {
name : String
});
};
Синхронизация моделей
Смотрите информацию в вики .
Удаление моделей
Смотрите информацию в вики .
Расширенные настройки
ORM2 позволяет вам выполнять некоторые дополнительные настройки определений вашей модели. Вы можете настроить их через настройки или при вызове define
при настройке модели.
Например, каждый экземпляр модели имеет уникальный идентификатор в базе данных. Этот столбец таблицы добавляется автоматически и по умолчанию называется "id".
Если вы определите свой собственный key: true
столбец, "id" не будет добавлен:
1
2
3
4
5
6
7
8
9
10
11
12var Person = db.define("person", {
personId : { type: 'serial', key: true },
name : String
});
// You can also change the default "id" property name globally:
db.settings.set("properties.primary_key", "UID");
// ..and then define your Models
var Pet = db.define("pet", {
name : String
});
Модель домашнего животного будет иметь 2 столбца: UID
и name
.
Также возможно иметь составные ключи:
1
2
3
4var Person = db.define("person", {
firstname : { type: 'text', key: true },
lastname : { type: 'text', key: true }
});
Другие варианты:
identityCache
: (по умолчанию:false
) Установите егоtrue
для включения кэша идентификаторов ( Singletons ) или установите значение таймаута (в секундах);autoSave
: (по умолчанию:false
) Установите сохранениеtrue
экземпляра сразу после изменения любого свойства;autoFetch
: (по умолчанию:false
) Установитеtrue
для извлечения ассоциаций при получении экземпляра из базы данных;autoFetchLimit
: (по умолчанию:1
) ЕслиautoFetch
этот параметр включен, он определяет, сколько пялец (ассоциаций ассоциаций) вы хотите автоматически получить.
Крючки
Смотрите информацию в вики .
Нахождение предметов
Model.getSync(id, [ options ])
Чтобы получить определенный элемент из базы данных, используйте Model.get
.
1
2var person = Person.getSync(123);
// finds person with id = 123
Model.findSync([ conditions ] [, options ] [, limit ] [, order ])
Поиск одного или нескольких элементов имеет больше возможностей, каждый из которых не может быть указан в определенном порядке параметров. Только options
должен быть после conditions
(даже если это пустой объект).
1
2var people = Person.findSync({ name: "John", surname: "Doe" }, 3);
// finds people with name='John' AND surname='Doe' and returns the first 3
Если вам нужно отсортировать результаты из-за ограничений или просто потому, что вы хотите их отсортировать, сделайте следующее:
1
2
3
4
5
6var people = Person.findSync({ surname: "Doe" }, "name");
// finds people with surname='Doe' and returns sorted by name ascending
people = Person.findSync({ surname: "Doe" }, [ "name", "Z" ]);
// finds people with surname='Doe' and returns sorted by name descending
// ('Z' means DESC; 'A' means ASC - default)
Есть и другие параметры, которые вы можете передать, чтобы что-то найти.Эти параметры передаются во втором объекте:
1
2var people = Person.findSync({ surname: "Doe" }, { offset: 2 });
// finds people with surname='Doe', skips the first 2 and returns the others
Вы также можете использовать необработанный SQL при поиске. Это описано в разделе «Цепочки» ниже.
Model.countSync([ conditions])
Если вы просто хотите подсчитать количество элементов, соответствующих условию, вы можете просто использовать .count()
вместо того, чтобы искать их все и подсчитывать. Это фактически скажет серверу базы данных выполнить подсчет (это не будет сделано в самом процессе узла). ).
1
2var count = Person.countSync({ surname: "Doe" });
console.log("We have %d Does in our db", count);
Model.existsSync([ conditions])
Подобно .count()
, этот метод просто проверяет, больше ли счетчик нуля или нет.
1
2var exists = Person.existsSync({ surname: "Doe" });
console.log("We %s Does in our db", exists ? "have" : "don't have");
Aggregating Functions
Можно передать несколько Array
свойств, чтобы выбрать только несколько свойств, а Object
также определить условия.
Вот пример, иллюстрирующий, как использовать .groupBy()
:
1
2
3//The same as "select avg(weight), age from person where country='someCountry' group by age;"
var stats = Person.aggregate(["age"], { country: "someCountry" }).avg("weight").groupBy("age").getSync();
// stats is an Array, each item should have 'age' and 'avg_weight'
Базовые .aggregate()
методы
.limit()
: вы можете передать число в качестве ограничения или два числа в качестве смещения и ограничения соответственно..order()
: такой же какModel.find().order()
Дополнительные .aggregate()
методы
min
max
avg
sum
count
(есть ярлык для этого -Model.count
)
В зависимости от драйвера существует больше агрегатных функций (например, математические функции).
Chaining
Если вы предпочитаете менее сложный синтаксис, вы можете создать цепочку .find()
, не указывая параметр обратного вызова.
1
2
3var people = Person.find({ surname: "Doe" }).limit(3).offset(2).only("name", "surname").runSync();
// finds people with surname='Doe', skips first 2 and limits to 3 elements,
// returning only 'name' and 'surname' properties
Если вы хотите пропустить только одно или два свойства, вы можете .omit()
вместо этого вызвать .only
.
Цепочка позволяет выполнять более сложные запросы. Например, мы можем выполнить поиск, указав собственный SQL:
1Person.find({ age: 18 }).where("LOWER(surname) LIKE ?", ['dea%']).allSync( ... );
Экранировать параметры SQL вручную — плохая практика, так как это чревато ошибками и подвергает ваше приложение SQL-инъекции. Синтаксис ?
позаботится об экранировании за вас, безопасно заменяя вопросительный знак в запросе предоставленными параметрами. Вы также можете объединить несколько where
предложений в цепочку, например нужный.
.find
, .where
& .all
делают одно и то же; все они взаимозаменяемы и могут быть объединены в цепочки.
Вы также можете order
или orderRaw
:
1
2
3Person.find({ age: 18 }).order('-name').allSync( ... );
// see the 'Raw queries' section below for more details
Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).allSync( ... );
Вы также можете связать и просто получить счетчик в конце. В этом случае смещение, лимит и порядок игнорируются.
1
2var people = Person.find({ surname: "Doe" }).countSync();
// people = number of people with surname="Doe"
Также доступна опция удаления выбранных элементов. Обратите внимание, что при последовательном удалении не будут запускаться какие-либо перехватчики.
1
2Person.find({ surname: "Doe" }).removeSync();
// Does gone..
Вы также можете вносить изменения в свои экземпляры, используя распространенные методы обхода массива, и в конце сохранять все.[НЕ ПОДДЕРЖИВАЕТСЯ]
1
2
3
4
5
6
7
8
9
10
11
12
13Person.find({ surname: "Doe" }).each(function (person) {
person.surname = "Dean";
}).save(function (err) {
// done!
});
Person.find({ surname: "Doe" }).each().filter(function (person) {
return person.age >= 18;
}).sort(function (person1, person2) {
return person1.age < person2.age;
}).get(function (people) {
// get all people with at least 18 years, sorted by age
});
Конечно, вы можете сделать это прямо на .find()
, но для некоторых более сложных задач это может быть очень полезно.
Model.find()
не возвращает массив, поэтому вы не можете просто создать цепочку напрямую. Чтобы начать цепочку, вам нужно вызвать
.each()
(с необязательным обратным вызовом, если вы хотите пройти по списку). Затем вы можете использовать общие функции
.filter()
, .sort()
и .forEach()
более одного раза.
В конце (или в процессе...) вы можете позвонить:
.countSync()
если вы просто хотите узнать, сколько предметов;.getSync()
получить список;.saveSync()
чтобы сохранить все изменения элемента.
Условия
Условия определяются как объект, где каждый ключ является свойством (столбец таблицы). Предполагается, что все ключи должны быть объединены логическим значением AND
. Значения считаются точно совпадающими, если только вы не передаете Array
. В этом случае считается, что список для сравнения объекта недвижимости.
1
2{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo'
{ col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5)
Если вам нужны другие сравнения, вам придется использовать специальный объект, созданный некоторыми вспомогательными функциями. Вот несколько примеров для его описания:
1
2
3
4
5
6
7
8
9
10
11{ col1: orm.eq(123) } // `col1` = 123 (default)
{ col1: orm.ne(123) } // `col1` <> 123
{ col1: orm.gt(123) } // `col1` > 123
{ col1: orm.gte(123) } // `col1` >= 123
{ col1: orm.lt(123) } // `col1` < 123
{ col1: orm.lte(123) } // `col1` <= 123
{ col1: orm.between(123, 456) } // `col1` BETWEEN 123 AND 456
{ col1: orm.not_between(123, 456) } // `col1` NOT BETWEEN 123 AND 456
{ col1: orm.like(12 + "%") } // `col1` LIKE '12%'
{ col1: orm.not_like(12 + "%") } // `col1` NOT LIKE '12%'
{ col1: orm.not_in([1, 4, 8]) } // `col1` NOT IN (1, 4, 8)
Необработанные запросы
1
2
3
4
5
6
7
8
9
10
11
12
13var data = db.driver.execQuerySync("SELECT id, email FROM user")
// You can escape identifiers and values.
// For identifier substitution use: ??
// For value substitution use: ?
var data = db.driver.execQuerySync(
"SELECT user.??, user.?? FROM user WHERE user.?? LIKE ? AND user.?? > ?",
['id', 'name', 'name', 'john', 'id', 55])
// Identifiers don't need to be scaped most of the time
var data = db.driver.execQuerySync(
"SELECT user.id, user.name FROM user WHERE user.name LIKE ? AND user.id > ?",
['john', 55])
Identity pattern
Вы можете использовать шаблон идентификации (по умолчанию отключен). Если этот параметр включен, несколько разных запросов приведут к одному и тому же результату - вы получите один и тот же объект. Если у вас есть другие системы, которые могут изменить вашу базу данных, или вам нужно вызвать какое-то руководство SQL-запросы, вам не следует использовать эту функцию. Также известно, что она может вызвать некоторые проблемы со сложными связями автоматической выборки. Используйте ее на свой страх и риск.
Его можно включить/отключить для каждой модели:
1
2
3
4
5var Person = db.define('person', {
name : String
}, {
identityCache : true
});
а также глобально:
1
2var db = orm.connectSync('...');
db.settings.set('instance.identityCache', true);
Кэш идентификаторов можно настроить на истечение срока его действия через определенный период времени, передав число вместо логического значения. Число будет считаться тайм-аутом кэша в секундах (вы можете использовать плавающую точку).
Примечание . Единственным исключением из кэширования является то, что оно не будет использоваться, если экземпляр не сохранен. Например, если вы извлекаете Person, а затем меняете его, пока он не сохранится, он не будет передан из кэша.
Создание предметов
Model.createSync(items)
Чтобы вставить новые элементы в базу данных, используйте Model.create
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var items = Person.createSync([
{
name: "John",
surname: "Doe",
age: 25,
male: true
},
{
name: "Liza",
surname: "Kollan",
age: 19,
male: false
}
]);
// items - array of inserted items
Обновление элементов
Каждый возвращаемый элемент имеет свойства, определенные в модели, а также несколько методов, которые можно использовать для изменения каждого элемента.
1
2
3
4
5var John = Person.getSync(1);
John.name = "Joe";
John.surname = "Doe";
John.saveSync();
console.log("saved!");
Обновление и последующее сохранение экземпляра можно выполнить одним вызовом:
1
2
3var John = Person.getSync(1);
John.saveSync({ name: "Joe", surname: "Doe" });
console.log("saved!");
Если вы хотите удалить экземпляр, просто выполните:
1
2
3
4// you could do this without even fetching it, look at Chaining section above
var John = Person.getSync(1);
John.removeSync();
console.log("removed!");
Валидации
Смотрите информацию в вики .
Ассоциации
Ассоциация — это связь между одной или несколькими таблицами.
hasOne
Отношение « многие к одному» . Это то же самое, что и «принадлежит».
Например: Animal.hasOne('owner', Person)
.
Животное может иметь только одного владельца, но у человека может быть много животных. Свойство
«Животное» будет owner_id
добавлено автоматически.
Станут доступны следующие функции:
1
2
3
4animal.getOwnerSync() // Gets owner
animal.setOwnerSync(person) // Sets owner_id
animal.hasOwnerSync() // Checks if owner exists
animal.removeOwnerSync() // Sets owner_id to 0
Цепной поиск
Ассоциация hasOne также совместима с поиском по цепочке. Используя приведенный выше пример, мы можем сделать это для доступа к новому экземпляру объекта ChainFind:
1Animal.findByOwner({ /* options */ })
Обратный доступ
1Animal.hasOne('owner', Person, {reverse: 'pets'})
добавим следующее:
1
2
3
4
5
6// Instance methods
person.getPetsSync(function..)
person.setPetsSync(cat, function..)
// Model methods
Person.findByPets({ /* options */ }) // returns ChainFind object
hasMany
Отношение многие-ко-многим (включая объединяющую таблицу).Например
: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })
.У
пациента может быть много разных врачей.У каждого врача может быть много разных пациентов.
Это создаст таблицу соединения patient_doctors
при вызове Patient.sync()
:
имя столбца | тип |
---|---|
идентификатор_пациента | Целое число (составной ключ) |
доктор_ид | Целое число (составной ключ) |
почему | варчар(255) |
Будут доступны следующие функции:
1
2
3
4
5
6
7
8
9
10
11
12patient.getDoctorsSync() // List of doctors
patient.addDoctorsSync(docs) // Adds entries to join table
patient.setDoctorsSync(docs) // Removes existing entries in join table, adds new ones
patient.hasDoctorsSync(docs) // Checks if patient is associated to specified doctors
patient.removeDoctorsSync(docs) // Removes specified doctors from join table
doctor.getPatientsSync()
etc...
// You can also do:
patient.doctors = [doc1, doc2];
patient.saveSync()
Чтобы привязать врача к пациенту:
1patient.addDoctorSync(surgeon, {why: "remove appendix"})
который добавит {patient_id: 4, doctor_id: 6, why: "remove appendix"}
в таблицу соединений.
getAccessor
Этот метод доступа в этом типе ассоциации возвращает a, ChainFind
если не передает обратный вызов. Это означает, что вы можете делать такие вещи, как:
1
2var doctors = patient.getDoctors().order("name").offset(1).runSync());
// ... all doctors, ordered by name, excluding first one
extendsTo
Если вы хотите разделить необязательные свойства на разные таблицы или коллекции, каждое расширение будет находиться в новой таблице, где уникальным идентификатором каждой строки является идентификатор экземпляра основной модели. Например:
1
2
3
4
5
6
7var Person = db.define("person", {
name : String
});
var PersonAddress = Person.extendsTo("address", {
street : String,
number : Number
});
Это создаст таблицу person
со столбцами id
и name
. Расширение создаст таблицу person_address
со столбцами person_id
и street
. number
Доступные в модели методы Person
аналогичны ассоциации hasOne
. В этом примере вы сможете вызвать .getAddress(cb)
, .setAddress(Address, cb)
, ..
Примечание: вам не нужно сохранять результат из Person.extendsTo
. Он возвращает расширенную модель. Вы можете использовать его для прямого запроса к этой расширенной таблице (и даже найти связанную модель), но это зависит от вас. Если вы хотите только получить к ней доступ используя исходную модель, вы можете просто отказаться от возврата.
Examples & options
Если у вас есть отношение от 1 до n, вам следует использовать hasOne
ассоциацию (принадлежит).
1
2
3
4
5
6
7
8
9
10
11
12
13var Person = db.define('person', {
name : String
});
var Animal = db.define('animal', {
name : String
});
Animal.hasOne("owner", Person); // creates column 'owner_id' in 'animal' table
// get animal with id = 123
var animal = Animal.getSync(123);
// animal is the animal model instance, if found
var person = animal.getOwnerSync();
// if animal has really an owner, person points to it
Вы можете пометить owner_id
поле как обязательное в базе данных, указав опцию required
:
1Animal.hasOne("owner", Person, { required: true });
Если поле не является обязательным, но должно быть проверено, даже если оно отсутствует, укажите этот alwaysValidate
параметр (это может произойти, например, когда проверка пустого поля зависит от других полей в записи).
1Animal.hasOne("owner", Person, { required: false, alwaysValidate: true });
Если вы предпочитаете использовать другое имя для поля (owner_id), вы можете изменить этот параметр в настройках.
1db.settings.set("properties.association_key", "{field}_{name}"); // {name} will be replaced by 'owner' and {field} will be replaced by 'id' in this case
Примечание. Это необходимо сделать до указания ассоциации.
Ассоциации hasMany
могут иметь дополнительные свойства в таблице ассоциаций.
1
2
3
4
5
6
7
8
9
10
11var Person = db.define('person', {
name : String
});
Person.hasMany("friends", {
rate : Number
}, {}, { key: true });
var John = Person.getSync(123);
var friends = John.getFriendsSync();
// assumes rate is another column on table person_friends
// you can access it by going to friends[N].extra.rate
Если вы предпочитаете, вы можете активировать autoFetch
, Таким образом, ассоциации автоматически извлекаются, когда вы получаете или находите экземпляры модели.
1
2
3
4
5
6
7
8
9
10
11
12var Person = db.define('person', {
name : String
});
Person.hasMany("friends", {
rate : Number
}, {
key : true, // Turns the foreign keys in the join table into a composite key
autoFetch : true
});
var John = Person.getSync(123);
// no need to do John.getFriends() , John already has John.friends Array
Вы также можете определить эту опцию глобально, а не для каждой ассоциации.
1
2
3
4
5
6
7
8
9
10var Person = db.define('person', {
name : String
}, {
autoFetch : true
});
Person.hasMany("friends", {
rate : Number
}, {
key: true
});
С помощью этой опции ассоциации могут выполнять вызовы связанной модели reverse
. Например, если у вас есть ассоциация ModelA с ModelB, вы можете создать метод доступа в ModelB для получения экземпляров из ModelA. Сбивает с толку? Посмотрите на следующий пример.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var Pet = db.define('pet', {
name : String
});
var Person = db.define('person', {
name : String
});
Pet.hasOne("owner", Person, {
reverse : "pets"
});
var pets = Person(4).getPetsSync();
// although the association was made on Pet,
// Person will have an accessor (getPets)
//
// In this example, ORM will fetch all pets
// whose owner_id = 4
Это имеет еще больший смысл при наличии hasMany
ассоциаций, поскольку вы можете управлять ассоциациями «многие ко многим»
с обеих сторон.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var Pet = db.define('pet', {
name : String
});
var Person = db.define('person', {
name : String
});
Person.hasMany("pets", Pet, {
bought : Date
}, {
key : true,
reverse : "owners"
});
Person(1).getPetsSync(...);
Pet(2).getOwnersSync(...);
Сопровождение сделок
Вы можете использовать функцию транзакции низкого уровня для обработки транзакции базы данных.
1
2
3
4
5
6db.begin();
...
if(err)
db.rollback();
else
db.commit();
Или вы можете использовать trans для упрощения его обработки.
1
2
3
4var result = db.trans(() => {
...
return result;
});
Добавление адаптеров внешних баз данных
Чтобы добавить адаптер внешней базы данных в orm
, вызовите addAdapter
метод, передав псевдоним, который будет использоваться для подключения к этому адаптеру, а также конструктор адаптера:
1require('orm').addAdapter('cassandra', CassandraAdapter);
Видетьthe documentation for creating adaptersБольше подробностей.