Использование базы данных IndexedDB

Использование IndexedDB

25 February 2014 г. 23:08:33

Браузеры предоставляют несколько способов сохранения вашим приложением пользовательских данных на клиентской стороне. Один из способов хранения это IndexedDB - web стандарт поддерживаемый многими браузерами. IndexedDB представляет собой реализацию индексной базы данных в которой, в которой каждая запись определяется индексом или ключом, чтобы сделать доступ к данным быстрее. При помощи IndexDB, вы можете хранить большие объемы структурированных данных, таких как изображения, массивы, словари и объекты. Стандарт не определяет ограничения по размеру конкретных элементов данных или самой базы данных, но многие браузеры могут накладывать ограничение по размеру хранилища.

IndexDB предоставляет некоторые преимущества. Ваше приложение

  • может иметь полностью функциональным, даже если подключение к сети недоступно

  • может кэшировать данные и восстанавливать состояние между вызовами.

  • не будет терять данные при разрывах сетевого соединения

  • генерирует меньше трафика.

  • выполняется быстрее, потому что манипуляции с данными проходят на вашем компьютере, а не в интренете.

Замечание Некоторые браузеры неподдреживают IndexedDB. Зайдите на caniuse.com и проверьте данные. Ваше приложение может программно проверять поддерживает ли платформа IndexedDB и настроить соответствующим образом.

В этой статье вы узнаете как испольщовать dart:indexed_db для хранения и поолучения данных при помощи IndexedDB браузера.

Запуск приложения

О приложении: основные моменты

Детали о IndexedDB

Импорт библиотеки IndexedDB

Проверка поддержки IndexedDB

Создание и открытие базы данных

Создание объекта базы данных

Использование именованных индексов

Использование транзакций

Добавление данных

Удаление данных

Очистка всех данных

Использование курсоров для получения записей

Запуск приложения

Ниже приложение count_down в котором список этопов и для каждого этапа таймер с обратным отсчетом.

Попробуйте! Введите имя, дату и вресмя этапа, можете указать например вашу дату рождения и нажмите на плюс (+). Приложение запускает обратный отсчет и показывает примерное оставшееся время до наступления события. Приложение обновляется каждую секунду.

Кнопка минус (-) справа от этапа, используется для удаления этапа. Используя кнопку Clear Вы можете удалить все этапы.

Использование инструментов разработчика чтобы взглянуть на базу данных

Вы можете воспользоватья инстументами разработчика для исследования базы данных IndexedDB, которая используется в Вашем приложении. Выберите View > Developer > Developer Tools в Хроме, а затем выберите закладку Resources в верху окна. Следущая диаграмма показывает базу данных использующуюся в приложении count_down с двумя этапами.

Внутри приложения count_down используется всего одна база данных названная milestoneDB и единственных объект названный milestoneStore. В этом примере каждая запись в объекте базы данных является этапом записанным в виде словаря. Индекс названый name_index ассоциируется с каждым именем этапом при помощи ключа базы данных позволяющий искать по названиям этапов.

IndexDB в инструментах разработчика

О приложении: основные моменты

Приложение count_down использует Модель (Model), Представление (View), Представление-модели (View-model) структуру (MVVM).

  • Представление-модели в центре дигаммы, связано с моделью и представлением, используя для обновления модели события пользовательского интерфейса и таймера. Класс MilestoneApp является основным классом который реализует Представление-модели - управляет таймером и реализует бизнес-логику приложения, которая управляет обменом информации между моделью и представлением.

  • Представление предоставляет пользователю интерфуйс приложения. Два пользовательских элемента объявленных в приложении count_down: CountDownComponent описывает пользовательский интерфейс приложения в целом и MilestoneComponet описывает пользовательский интерфейс каждого этапа. Эти компоненты информируют представление-модели о событиях в пользовательском интерфейсе.

  • Модель содержит и управляет данными. Можедель, объявляет класс MilestoneStore управляет в памяти списком объектов этапов и сохраняет синхранизацию IndexedDB со списком, сохраняя этапы принудительно. Представление-модели запрашивает Модель во время инициализации и использует Polymer для связывания данных и синхронизирования представления. Также она использует события таймера для вызова обновления модели.

Библиотеки использованные в приложении count_down

Приложение count_down использует следующие библиотеки:

БиблиотекаОписание
dart:indexed_db Принудительно сохраняет данные в базу данных IndexedDB для оффлайн совместимости
dart:async Асинхронное выполнение задач с использованием фьючеросов
dart:core Использует DateTime и Duration для выполнения задач связанных со временем
Polymer Создает пользовательские элементы и связывает данные

Эта статья объясняет как в приложении IndexedDB используется Dart API.

Замечание: Эта статья не содержит описания фьючерсов и Polymer. Чтобы получить больше информации посмотрите статьи Использование API для фьючерсов и Фьючерсы и обработка ошибок. Для большей информации о Polymer обратитесь к Определению пользовательских элементов.

Детали о IndexedDB

Несколько важных фактов, которые вам надо знать о IndexedDB:

  • Каждый отдельный (хост, протокол и порт) имеет свою собственную базу данных. Уникальное имя идентифицирует каждую базу данных в пределах ее происхождения. Для IndexedDB действует та же политика безопасности которая требует чтобы происхождение базы данных и приложения были одинаковы.

  • База данных идентифицируется по имени и версии. База данных может иметь только одну версию за раз.

  • (Объект базы (object store) идентифицируется по уникальному имени. Вы можете создать объект базы только через событие "upgrade needed". Тем самым вы сохраняется данные в виде записи в объекте базы. База данных может иметь множество именованных объектов базы.

  • Транзакции предоставляют надежный доступ к данным и изменениям в базе данных. Все взаимодействия с данными в базе должны происходить в пределах транзакции.

  • Запись пара ключ/значение, где ключ это уникальный идентификатор для передаваемого значения. Вы можете задать свой собственный ключ или база объектов может создавать их за вас

  • Индекс специальный объект базы который соединяет ключи базы данных с ключевыми полями сохраненного объекта. Использование индексов не обязательно.

  • Приложение может использовать множество баз данных, каждая из который содержит множество объектов базы, каждый из которых содержит множество записей.

IndexedDB в приложении count_down

Приложение count_down использует только одну базу данных названную milestoneDB, номер версии которой 1 и объект базы названный milestoneStore. Приложение сохраняет объекты milestone в виде словаря. Ключ и значение в этом словаре являются строками. Поле milestoneName* уникальное имя этапа. Поле happensOn** это объединенные вместе дата и время. Этой информации достаточно для того чтобы сохранять состояние приложения когда оно пере запущено.

Схема работы Indexeddb

Так как название этапов уникально, приложение может использовать его как первичный ключ в базе данных. Однако, для иллюстрации возможностей, приложение использует первичный ключ сгенерированный базой данных. Вы можете увидеть это используя окно инструментов разработчика:

Импорт библиотеки IndexedDB

Чтобы использовать в Dart IndexDB API, Ваше приложение должно импортировать библиотеку IndexedDB предоставленную Dart SDK:

import ‘dart:indexed_db’

Проверка поддержки IndexedDB

Используйте геттер supported из класса IdbFactory для определения поддерживается ли IndexedDB.

bool idbAvailable = IdbFactory.supported;

Если supported == false, ваше приложение может:

  • Выдать исключение и выйти,

  • использовать альтернативные API, такие как windiw.localStorage, для сохранения даннх на стороне клиента,

  • запустить все равно, пожертвовав устойчивостью приложения и оффлайн совместимостью,

  • или, как сделано в приложении count_down, отключить пользовательский интерфейс и выдать сообщение об ошибке.

Создание и открытие базы данных

Используйте window.indexedDB.open() для создания новой базы данных или открытия существующей. И открытие И создание базы данных зависит от передаваемых параметров имени и номера версии базы даннх.

  • Используйте имя и текущий номер версии существующей базы данных, чтобы открыть базу данных.

  • Для обновления версии базы вызовите open() с именем существующей базы данных и более высоким номером версии. (База данных может иметь только одну версию за раз, то есть за раз не может быть множества версий). Это запускает событие для обновления.

  • Для создания полностью новой базы данных, откройте ее с новым уникальным именем. Это также вызовет событие обновления. А вот функция из класса MilestoneStore, которая создает и открывает базу данных:

    Future open() {
        return window.indexedDB.open('milestoneDB',
        version: 1,
        onUpgradeNeeded: _initializeDatabase)
      .then(_loadFromDB);
    }

Первые два параметра означают номер и версию базы данных которую надо открыть. В первый раз приложение coun_down запустится из данного источника, с названием milestoneDB и номером версии 1, что создаст базу и откроет ее. В следующий раз приложение запустится из того же источника, и база данных будет просто открыта.

Третий параметр onUpgradeNeeded, предоставляет колбэк функцию, которая будет вызвана в тот момент когда будет вызвано событие обновления. Это случится когда создается новая база данных или при обновлении версии базы данных. Это дает возможность вашему приложению создать объект базы. Единственное место в котором вы можете создать объект базы это при вызове события обновления. Объект базы должен быть создан обязательно для добавления записей базу данных. В следующей части будет рассказано о том как создать объект базы.

Следующая диаграмма описывает логику функции window.indexedDB.open().

Так как создание и открытие базы данных может занимать некоторое время window.indexedDB.open() возвращает объект типа Future, который выполняется асинхронно и возвратит объект базы данных когда нибудь в будущем. Объект базы данных вернется в колбэк функцию зарегистрированную как this(). В этом примере колбэк функция называется _loadFromDB. Используя курсоры, _loadFromDB() читает из базы данных этапы и отображает их в приложении. Больше информации в разделе Использование курсоров для получение записей.

Создание объекта базы данных

Когда новая база данных создана в ней нет объектов базы. Единственное место где вы можете создать объект базы это при выполнении события обновления. К счастью это событие вызывается при создании базы данных (или обновления версии базы данных).

В приложении count_down, колбэк функция для события обновления базы называется _initializeDatabase. Эта функция создает объект базы данных и индекс.

static const String MILESTONE_STORE = 'milestoneStore';
static const String NAME_INDEX = 'name_index';

void _initializeDatabase(VersionChangeEvent e) {
  Database db = (e.target as Request).result;

  var objectStore = db.createObjectStore(MILESTONE_STORE,
      autoIncrement: true);
  var index = objectStore.createIndex(NAME_INDEX, 'milestoneName',
      unique: true);
}

Этот код берет объект базы данных из объекта типа VersionChangeEvent, который передается в колбэк функцию параметром.

Используйте метода базы данных createObjectStore() для создания нового объекта базы с указанным именем. Каждый объект базы должен иметь уникальное имя. Приложение count_down использует один объект базы названный milestoneStore. Обратный отсчет для всех этапов сохраняется и получается из объекта базы.

В коде устанавливается свойство объекта autoIncrement в true. Когда autoIncrement установлен в true база даннх генерирует уникальные первичные ключи за вас, которые решают проблему неуникальности ключей.

И наконец _initializeDatabase создает имя индекса.

Использование именованных индексов

Индексы предоставляют поиск по таблице. Вы можете связать первичный ключ с полем в объекте базы. Например индекс связан первичным ключом в поле milestoneName.

Использование индексов дает два преимущества:

  • вы моете выполнять поиск по полям в объекте вместо первичного ключа,

  • и вы можете использовать индексы чтобы быть уверенным что значения поля уникальны.

Приложение count_down создает индекс во время создания объекта базы, во время выполнения события обновления, и это единственное место где Вы можете создать индекс.

static const String NAME_INDEX = 'name_index';
...
objectStore.createIndex(NAME_INDEX, 'milestoneName', unique: true);

Метод createIndex принимает три параметра:

  • имя индекса, которое должно быть уникальным. В даннм примере это 'name_index'.

  • Путь к ключу, который определяет поле для индекса в объекте базы.

  • Уникальность (unique), булевское поле. Когда оно установлено в true индекс следит за тем чтобы имя этапа оставалось уникальным. Если вы попробуете, приложении count_down, добавить этап с именем, которое уже есть у другого этапа в этом индексе, то функция add() выдаст ошибку.

Использование транзакций

Все операции с базой данных должны выполняться с использованием Транзакций

Важно о жизненном цикле транзакций

Жизненный цикл транзакций следующий:

  • Открыть транзакцию

  • Поместить запрос в транзакцию и зарегистрировать колбэк

  • Когда запросов больше не остается и завершается последний колбэк, транзация считается выполненной.

Итак, транзакции остаются в работе пока выполняются все запросы и заканичвается при выполнении последнего колбэека. Вы можете продолжить выполнение транзакции помещая новые запросы с колбэками.

Хорошее правило: во время выполнения области транзакции, выполняются только задачи связанные с базой данных, для избежания сумасшедших ошибок связанных с последовательностью обработки DOM.

Возмем транзакцию из объекта базы данных приложения count_down, который называется _db.

Transaction t = _db.transaction(storeNameOrNames, mode);

Первый параметр передаваемый в transaction() это область транзакции. В приложении count_down область всегда milestoneStore - единственный объект базы данных, но вы можете указать множество объектов базы. Для повышения эффективности Вы должны указывать только те объекты, которые необходимы в приложении.

Вторым параметром передается строка, которая определяет режим транзакции, он может быть readwrite, readonly или versionchange. Приложение count_down использует только транзакции типа readwrite и readonly. Используйте транзакции readwrite только если это действительно необходимо, транзакции readonly более эффективные и Вы можете запускать одновременно множество транзакций только на чтение одноверменно, для перекрывающейся области, но только одна транзакция на чтение и запись может быть запущена за раз.

Так как Вы создаете транзацию, Вы можете выполнить одну или более операций используя эту транзакцию. Операции с базой данных занимают время, поэтому работа авполняется вне основного потока пользовательского интерфейсаи результат предоставляется через фьючерс. Каждая операция использует фьючерс, и завершается при завершении операции. Транзакия сама по себя также является фьючерсом, который завершается когда все операции в транзакции выполнены.

Например вы можете использовать одну транзакцию чтобы добавить множество записей в базу объекта. Каждое добавление это отдельная операция использующая фьючерс. Диаграмма ниже показывает логику следующей программы, которая добавляет три записи в базу данных. Когда все три операции добавления выполнены, транзакция также выполняется.

Жизненный цикл транзакции в IndexedDB

Многие базы данных использующие транзакции следуют данному шаблону:

  • Создание транзакции в объекте базы.

  • Выполнение одной или более операций использующей транзакции.

  • Использование в операциях колбэк функций, для выпонения задач для случаев, когда операция завершается успешно. Например, когда добавляется запись в базу данных вы можете получить ключ сгенерированный базой данных при добавлении записи.

  • Использование колбэка transaction.completed для подготовки задачи для случаяя когда все операции завершаются успешно. В общем, приложение count_down использует эту колбэк функцию для сохранения списка этапов синхронизированным с базой данных.

Добавление данных

Вот код, который который добавляет новые записи в базу данных.

Код создает новый объект Milestone и конвертирует его в словарь, затем создает новую транзакцию на чтение и запись (readwrite) для объекта базы.

Затем вызывается метод add() объекта базы, для добавления словаря этапа в базу данных и затем, при помощи then() регистрирует колбэк функцию. Так как объект базы был создан с параметром autoIncrement: true, добавление записи в базу данных автоматически создает для новой записи уникальный первичный ключ. Этот ключ возвращается как параметр для колбэк функции.

И наконец, в коде регистрируется колбэк функция для транзакции.

Когда в базе данных выполняется операция добавления, фьючерс относящийся к операции добавления завершается и вызывается колбэк функция с авто-генерированным ключом. Приложение count_down сохраняет ключи в объекте Milestone.

С этой точки зрения, важно отметить что несмотря на то что операция добавления завершается, транзакция нет! Любые изменения сделанные в базе данных при помощи любых изменений не доаступны пока транзакция не завершена.

В том случае, когда все операции в транзакции завершены, отдельная операция вствки в транзакции завершается и вызыватеся колбэк функция. В колбэк функциях транзакции, приложение count_down добавляет этап в список в памяте и фьючерс возвращается с новым объектом этапа (и Модель-представление запускает таймер)

Удаление данных

Вот код который удаляет пары ключ/значение из базы данных.

var transaction = _db.transaction(MILESTONE_STORE, 'readwrite');
transaction.objectStore(MILESTONE_STORE).delete(milestone.dbKey);

return transaction.completed.then((_) {
  milestone.dbKey = null;
  milestones.remove(milestone);
});

И опять код создает тразакцию на чтение и запись для определенного объекта базы. Чтобы удалить пару ключ/значение режим транзакции должен быть readwrite. Используя метод delete() объект базы удалят пару ключ/значение. Ключ передается в качестве параметра функции delte().

Когда транзакция завершается успешно, приложение count_down безопасно удаляет этап из списка в памяти и останавливает таймер, если необходимо.

Заметьте что этот код не определяет колбэк функцию для операции удаления, только для транзакции. Эта задача нуждается в выполнении, а именно удаление этапа из списка в памяти, может произойти когда транзакция выполняется.

Очистка всех данных

Для удаления всех записей используется метод Clear() объекта базы.

var transaction = _db.transaction(MILESTONE_STORE, 'readwrite');
transaction.objectStore(MILESTONE_STORE).clear();

return transaction.completed.then((_) {
  milestones.clear();
});

Использование курсоров для получения записей

Если при запуске приложения оказывается, что база данных существует и в этой базе данных есть этапы, то приложение count_down читает записи из базы данных и инициализирует список этапов для информации.

Метод _loadFromDB() вызывается сразу после того как база данных была успешно открыта. Внутри этого метода происходит чтение базы данных.

Future _loadFromDB(Database db) {
  _db = db;

  var trans = db.transaction(MILESTONE_STORE, 'readonly');
  var store = trans.objectStore(MILESTONE_STORE);

  var cursors = store.openCursor(autoAdvance: true).asBroadcastStream();
  cursors.listen((cursor) {
    var milestone = new Milestone.fromRaw(cursor.key, cursor.value);
    milestones.add(milestone);
  });

  return cursors.length.then((_) {
    return milestones.length;
  });
}

Так как при чтении записей не происходит изменения базы данных, транзакция открывается только на чтение.

Вы можете использовать объект Cursor чтобы один за одним пройти по всем записям в базе данных, создавая для каждого объект Milestone. Объект базы использует поток для вызова события для каждой записи полученной из базы данных. Прослушивая поток Ваш код может вызываться для каждой прочитанной записи. Для каждой полученной записи, приложение count_down создает передаваемый объект Milestone во внутренний список в памяти.

Открытие курсора в объекте транзакции происходит при помощи openCursor. Курсор отображает текущую позицию в базе данных. Приложение count_down открывает курсор и устанавливает необязательный аргумент autoAdvance в true. Это означает что после того как запись прочитана из базы данных и возвращено его значение, крусор автоматически перемещается на следующую запись. Если Вы не будете использовать autoAdvance, Вам надо перемещать курсор на следующую запись самостоятельно используя метод next().

Работа с курсором в IndexedDb

Метод openCursor() возврашает поток, в котором вы можите прослушивать события. Этот поток является продкастом, поэтому приложение может прослушивать сотия как для чтения, так и для получения данных для отсчета.

Для каждой полученной из базы данных записи вызывается срабатывает событие и вызывается колбэк функция. Объект CursorWithValue, названный в этом примере cursor передается в колбэк функцию. Используйте cursor.key для получения ключей для только что полученной записи. Используйте cursor.value для получения значения записи.

Метод _loadFromDB возвращает фьючерс, который возвращает длинну потока.


Оставьте свой комментарий

comments powered by Disqus
Меню

Cult of digits 2014 Яндекс.Метрика