Объектно-реляционное отображение для fibjs
Установить
1npm install fib-orm
Тестовое задание
1npm run ci
Поддержка СУБД
- MySQL и MariaDB
- 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 столбца: an UID
и a name
.
Также возможны составные ключи:
1
2
3
4var Person = db.define("person", {
firstname : { type: 'text', key: true },
lastname : { type: 'text', key: true }
});
Другие варианты:
identityCache
: (по умолчанию :)false
Установитеtrue
для включения кеширования идентификационных данных ( Синглтоны ) или установите значение тайм-аута (в секундах);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, а затем изменяете его, пока он не будет сохранен, он не будет передан из Cache.
Создание предметов
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
A МНОЖЕСТВО к одному . Это отношение одновременно, КАК принадлежит к.
Например: 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
А является многими ко многим отношений (включает Join Table).
Например , из: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })
.
. Пациента Каждого врача может иметь доктора есть МНОГО CAN Различные Различные пациенты имеют много.
Это создаст объединенную таблицу patient_doctors
при вызове Patient.sync()
:
имя столбца | тип |
---|---|
пациент_ид | Целое число (составной ключ) |
doctor_id | Целое число (составной ключ) |
Почему | варчар (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
Этот 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
модели, аналогичные AN, являются 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 Больше подробностей.