Много-файловое хранилище Java объектов в формате xml (часть 2)
В программировании часто перед нами встают задачи, которые мы можем решить несколькими путями: найти и использовать уже готовые решения, или же решать задачу самостоятельно. Хоть и написано множество спецификаций и их реализаций, они не всегда дают нам то, что требуется в конкретном случае. Вот и мне в очередной раз пришлось столкнуться с подобной ситуацией.
Задача состояла в хранении объектов в файле в формате xml. Ничего казалось бы сложного, если бы не несколько «но». Объектов много, имеют они древовидную структуру и над ними постоянно выполняются операции добавления, изменения и удаления в разных потоках. Как вы понимаете постоянные запись и чтение большого xml файла довольно трудоемкая задача. Тем более если с одними и теми же данными работают несколько потоков. Так собственно и родилась идея написать много-файловое хранилище объектов в формате xml.
В этой статье я не буду рассматривать саму реализацию. Приведу лишь основные идеи и как использовать эту реализацию. Если вы хотите углубиться, то можете скачать посмотреть исходные коды.
Исходники доступны по ссылке: xdstore-1.3
Исходные тексты немного отличаются от приведенных в этой статье. В них были глубже проработаны исключительные ситуации, а именно, — для каждой операции, включая чтение, выбрасывается свое исключение. Также в последней версии реализована фрагментация.
Главная идея заключается в том, чтобы объекты хранить не в одном файле, а в некотором множестве. При этом предоставить возможность настраивать политики хранения для каждого требуемого класса. Для класса можно установить одну из следующих политик:
Под понятием объектной ссылки понимается объект указанного класса, у которого проставлено одно поле – идентификатор. В xml файле вместо полных данных этого объекта сохраняется лишь имя класса и идентификатор, чтобы в дальнейшем по этой ссылке можно было получить все данные. Загрузка таких объектов подобна поздней инициализации в hibernate.
Сохраняемые объекты должны быть реализованы как JavaBeans с методами get(is) и set для сохраняемых полей.
Чтобы лучше понять ситуацию, в которую мы попадаем при попытке реализовать такое хранилище, необходимо правильно поставить задачу. В терминах БД звучит она следующим образом: в таблице базы данных имеются две строки, одновременно начинаются две транзакции, каждая из которых модифицирует обе строки, затем завершается коммитом первая транзакция и начинается третья, которая также модифицирует эти две строки.
Нас интересует поведение в подобной ситуации, т.е. что произойдет с данными в каждой из транзакций. В текущей реализации библиотеки поведение будет следующим:
1) Поскольку данные были модифицированы первой транзакцией, то вторая транзакция получит отказ на изменение данных в виде исключения. Объясняется это тем, что первая и вторая транзакции начались в одно время и скорее всего работали с одинаковыми копиями, и чтобы не потерять изменения первой транзакции второй необходимо отказать.
2) А вот данные третьей транзакции будут приняты, поскольку она началась после коммита первой транзакции и работает с обновленными данными.
Поскольку это довольно простая реализация, то при решении поставленной задачи не использовались блокировки записей чтобы избежать deadlock-ов и необходимости отката транзакций по таймауту. В этом случае выбрасывается исключение, по которому транзакция должна быть откачена.
Сама цель данной разработки получить простую и гибкую библиотеку, позволяющую сохранять объекты в формате xml. Поэтому получившийся интерфейс довольно прост, а требования предъявляемые к сохраняемым объектам сведены к минимуму. Основное требование для каждого сохраняемого объекта заключается в необходимости реализовать простой интерфейс IXmlDataStoreIdentifiable. Выглядит он следующим образом:
Как вы можете видеть, необходимо лишь реализовать два метода работы с идентификатором объекта. Это необходимое условие обусловлено тем, что при некоторых политиках сохраняются лишь ссылки на объекты, по которым в дальнейшем может потребоваться восстановить (загрузить) все свойства. Ссылка в xml файле выглядит следующим образом:
При загрузке этой ссылки будет создан объект указанного класса и у него проставлено свойство идентификатора. Остальные поля будут проинициализированы по умолчанию, т. е. они не будут загружены.
Рассмотрим теперь простой пример настройки хранилища для хранения объектов следующих классов: XdUniverse и XdGalaxy. Для начала определим их классы.
И простенький класс XdGalaxy.
Теперь можно рассмотреть настройку хранилища для указанных сущностей.
Сейчас мы выбрали настройки, что все объекты каждого из классов будут храниться в своем файле, т.е. для каждого класса один файл. Можно использовать другие настройки и, например, не указывать политику для класса XdGalaxy, — тогда его объекты будут сохраняться вместе с объектами класса XdUniverse.
В результате для наших настроек после записи объектов мы получим два файла: XdUniverse.xml и XdGalaxy.xml.
Как видно из примера в этом файле хранятся ссылки на объекты из второго файла XdGalaxy.xml, приведенного ниже.
Таким образом мы получили двух файловое хранилище для наших объектов. Если нам не требуются объекты класса XdGalaxy, то мы можем загрузить лишь объекты класса XdUniverse и работать с ними. Если же нам потребуются объекты класса XdGalaxy, то нам достаточно загрузить их по уже загруженным ссылкам.
В случае, если мы поставим политику хранения объектов SingleObjectFile, в корневом каталоге хранилища будет создана папка, в которую и будут сохраняться файлы объектов.
Рассмотрим интерфейс класса XmlDataStore, касающийся операций сохранения объектов. Он довольно прост и позволяет нам сохранять объекты без указания политик, поскольку они уже проставлены при инициализации хранилища.
Хранилище разрабатывалось для многопоточного использования и в ходе работы может быть задействовано несколько ресурсных объектов, поэтому оно использует механизм транзакций и предоставляет соответствующие методы. Принятие и откат транзакции могут быть вызваны также через методы объекта самой транзакции.
Сохранение корневых объектов и дочерних объектов немного отличаются, поэтому методы работы над корневыми объектами выделены в отдельную группу. Отличие заключается в том, что при политике SingleObjectFile для каждого корневого объекта будет выделен отдельный файл и в дополнение для них всех создан дополнительный файл, в котором будут храниться ссылки. Это позволяет разом загрузить все корневые объекты.
Теперь рассмотрим операцию сохранения.
Из примера видно, что сохранить объекты довольно просто. Отметим лишь тот момент, что поскольку объекты класса XdGalaxy сохраняются в отдельном файле, нам необходимо явно выполнить операцию их сохранения. Их можно также сохранять по отдельности используя другой метод, описанный выше. Сама запись объектов в файл происходит при выполнении операции принятия транзакции и до тех пор пока она не вызвана все операции производятся с кэшем.
Рассмотрим теперь часть интерфейса, относящуюся к загрузке объектов из хранилища.
Как видно, хранилище позволяет нам загрузить разом все корни указанного класса или же запросить один корень указанного класса по идентификатору. Также можно загрузить объекты любого класса по ссылке или идентификатору. В нашем случае загрузка всех сохраненных данных будет выглядеть следующим образом.
Из примера видно, что сначала загружаются все корни, а затем для каждого корня по объектным ссылкам загружаются все дочерние объекты.
Методы обновления (изменения) и удаления объектов представлены ниже.
Следует отметить, что все зависимые объекты, которые хранятся в отдельных от владельца файлах, должны быть явно обновлены или удалены. Например, в нашем случае при удалении объекта класса XdGalaxy из объекта XdUniverse необходимо обновить объект XdUniverse и дополнительно явно удалить XdGalaxy.
В случае добавления объекта код выглядит следующим образом.
Если же политика сохранения ParentObjectFile, то для дочерних объектов нет необходимости явно выполнять операции удаления и сохранения, поскольку после обновления объекта владельца необходимая операция будет выполнена автоматически.
Полная очистка нашего хранилища будет выглядеть следующим образом:
Из примера видно, что нам даже не потребовалось загружать объекты класса XdGalaxy перед удалением. Мы просто передали коллекцию объектных ссылок. Это возможно поскольку объектная ссылка хранит идентификатор объекта.
Для повышения производительности работы хранилища используется неотключаемое кэширование. Т.е. при работе с любым ресурсным объектом (файлом) все хранимые в нем объекты загружаются и кэшируются при первой транзакции. Все остальные транзакции работают с уже кэшированными данными. Данные кэша сбрасываются, когда завершается последняя транзакция, которая работает с этим ресурсным объектом. Все изменения регистрируются в кэше и не сбрасываются на диск до тех пор, пока не происходит принятие транзакции.
Поскольку в ходе выполнения транзакции может быть затронуто неопределенное количество ресурсных объектов, то операция принятия изменений транзакции выполняется над всеми поочередно. Если при этом процессе происходит какая-либо ошибка, то целостность хранилища данных нарушается и выбрасывается исключение типа XmlDataStoreRuntimeException. В текущей реализации восстановление целостного состояния хранилища не реализовано. Это один из существенных недостатков текущей версии.
В текущей реализации при большом количестве объектов определенного класса и политике хранения ClassObjectsFile, трудоемкость операций чтения и записи растет прямо пропорционально росту количества объектов. Для того чтобы повысить производительность хранилища планируется реализовать фрагментацию и построение файла индекса. Фрагментация подразумевает под собой разбиение одного файла на фрагменты, содержащие ограниченное количество объектов, а индекс в данном случае будет содержать ссылки с указанием файла фрагмента, в котором сохранен объект.
Также в планы входит реализация восстановления целостного состояния хранилища после сбоя при принятии изменений транзакции.
Возможно, что в новой реализации хранилища появятся триггеры, которые будут вызываться при изменении состояния хранимых объектов. Т.е. при добавлении, изменении или удалении объектов.
Автор: Бесчастный Евгений
Много-файловое хранилище Java объектов в формате xml (часть 2)
Введение
В программировании часто перед нами встают задачи, которые мы можем решить несколькими путями: найти и использовать уже готовые решения, или же решать задачу самостоятельно. Хоть и написано множество спецификаций и их реализаций, они не всегда дают нам то, что требуется в конкретном случае. Вот и мне в очередной раз пришлось столкнуться с подобной ситуацией.
Задача состояла в хранении объектов в файле в формате xml. Ничего казалось бы сложного, если бы не несколько «но». Объектов много, имеют они древовидную структуру и над ними постоянно выполняются операции добавления, изменения и удаления в разных потоках. Как вы понимаете постоянные запись и чтение большого xml файла довольно трудоемкая задача. Тем более если с одними и теми же данными работают несколько потоков. Так собственно и родилась идея написать много-файловое хранилище объектов в формате xml.
В этой статье я не буду рассматривать саму реализацию. Приведу лишь основные идеи и как использовать эту реализацию. Если вы хотите углубиться, то можете скачать посмотреть исходные коды.
Исходники доступны по ссылке: xdstore-1.3
Исходные тексты немного отличаются от приведенных в этой статье. В них были глубже проработаны исключительные ситуации, а именно, — для каждой операции, включая чтение, выбрасывается свое исключение. Также в последней версии реализована фрагментация.
Основная идея разработки
Главная идея заключается в том, чтобы объекты хранить не в одном файле, а в некотором множестве. При этом предоставить возможность настраивать политики хранения для каждого требуемого класса. Для класса можно установить одну из следующих политик:
- ParentObjectFile – объекты класса будут сохраняться в файле объекта владельца как дочерние элементы, эта политика применяется по умолчанию;
- SingleObjectFile – каждому объекту класса предоставляется отдельный файл, а в файле объекта владельца будет сохранена лишь ссылка на этот объект (в дальнейшем буду просто называть ее объектной ссылкой); все файлы каждого объекта будут сохраняться в отдельной папке внутри хранилища;
- ClassObjectsFile – все объекты этого класса будут храниться в отдельном файле, а в файлах объектов владельцев будут сохранены лишь объектные ссылки.
Под понятием объектной ссылки понимается объект указанного класса, у которого проставлено одно поле – идентификатор. В xml файле вместо полных данных этого объекта сохраняется лишь имя класса и идентификатор, чтобы в дальнейшем по этой ссылке можно было получить все данные. Загрузка таких объектов подобна поздней инициализации в hibernate.
Сохраняемые объекты должны быть реализованы как JavaBeans с методами get(is) и set для сохраняемых полей.
Одна интересная задача
Чтобы лучше понять ситуацию, в которую мы попадаем при попытке реализовать такое хранилище, необходимо правильно поставить задачу. В терминах БД звучит она следующим образом: в таблице базы данных имеются две строки, одновременно начинаются две транзакции, каждая из которых модифицирует обе строки, затем завершается коммитом первая транзакция и начинается третья, которая также модифицирует эти две строки.
Нас интересует поведение в подобной ситуации, т.е. что произойдет с данными в каждой из транзакций. В текущей реализации библиотеки поведение будет следующим:
1) Поскольку данные были модифицированы первой транзакцией, то вторая транзакция получит отказ на изменение данных в виде исключения. Объясняется это тем, что первая и вторая транзакции начались в одно время и скорее всего работали с одинаковыми копиями, и чтобы не потерять изменения первой транзакции второй необходимо отказать.
2) А вот данные третьей транзакции будут приняты, поскольку она началась после коммита первой транзакции и работает с обновленными данными.
Поскольку это довольно простая реализация, то при решении поставленной задачи не использовались блокировки записей чтобы избежать deadlock-ов и необходимости отката транзакций по таймауту. В этом случае выбрасывается исключение, по которому транзакция должна быть откачена.
Начало использования
Сама цель данной разработки получить простую и гибкую библиотеку, позволяющую сохранять объекты в формате xml. Поэтому получившийся интерфейс довольно прост, а требования предъявляемые к сохраняемым объектам сведены к минимуму. Основное требование для каждого сохраняемого объекта заключается в необходимости реализовать простой интерфейс IXmlDataStoreIdentifiable. Выглядит он следующим образом:
public interface IXmlDataStoreIdentifiable {
String getId();
void setId(String id);
}
Как вы можете видеть, необходимо лишь реализовать два метода работы с идентификатором объекта. Это необходимое условие обусловлено тем, что при некоторых политиках сохраняются лишь ссылки на объекты, по которым в дальнейшем может потребоваться восстановить (загрузить) все свойства. Ссылка в xml файле выглядит следующим образом:
<reference class="org.flib.xdstore.entities.XdGalaxy" id="cc74e3f2"/>
При загрузке этой ссылки будет создан объект указанного класса и у него проставлено свойство идентификатора. Остальные поля будут проинициализированы по умолчанию, т. е. они не будут загружены.
Рассмотрим теперь простой пример настройки хранилища для хранения объектов следующих классов: XdUniverse и XdGalaxy. Для начала определим их классы.
package org.flib.xdstore.entities;
import java.util.Collection;
import org.flib.xdstore.IXmlDataStoreIdentifiable;
public class XdUniverse implements IXmlDataStoreIdentifiable {
private String id;
private Collection<XdGalaxy> galaxies;
@Override
public String getId() {
return id;
}
@Override
public void setId(final String id) {
this.id = id;
}
public Collection<XdGalaxy> getGalaxies() {
return galaxies;
}
public void setGalaxies(Collection<XdGalaxy> galaxies) {
this.galaxies = galaxies;
}
public void addGalaxy(XdGalaxy galaxy) {
galaxies.add(galaxy);
}
public XdGalaxy removeGalaxy() {
final Iterator<XdGalaxy> it = galaxies.iterator();
XdGalaxy galaxy = null;
if(it.hasNext()) {
galaxy = it.next();
it.remove();
}
return galaxy;
}
}
И простенький класс XdGalaxy.
package org.flib.xdstore.entities;
import org.flib.xdstore.IXmlDataStoreIdentifiable;
public class XdGalaxy implements IXmlDataStoreIdentifiable {
private String id;
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
}
Теперь можно рассмотреть настройку хранилища для указанных сущностей.
final XmlDataStore store = new XmlDataStore("./teststore");
store.setStorePolicy(XdUniverse.class, XmlDataStorePolicy.ClassObjectsFile);
store.setStorePolicy(XdGalaxy.class, XmlDataStorePolicy.ClassObjectsFile);
Сейчас мы выбрали настройки, что все объекты каждого из классов будут храниться в своем файле, т.е. для каждого класса один файл. Можно использовать другие настройки и, например, не указывать политику для класса XdGalaxy, — тогда его объекты будут сохраняться вместе с объектами класса XdUniverse.
В результате для наших настроек после записи объектов мы получим два файла: XdUniverse.xml и XdGalaxy.xml.
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<object isNull="false" class="org.flib.xdstore.entities.XdUniverse" id="002df141">
<collection name="Galaxies" class="java.util.ArrayList">
<reference class="org.flib.xdstore.entities.XdGalaxy" id="cc74e3f2"/>
<reference class="org.flib.xdstore.entities.XdGalaxy" id="ca519d20"/>
</collection>
<object name="Id" isNull="false" class="java.lang.String" value="002df141"/>
</object>
</objects>
Как видно из примера в этом файле хранятся ссылки на объекты из второго файла XdGalaxy.xml, приведенного ниже.
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<object isNull="false" class="org.flib.xdstore.entities.XdGalaxy" id="cc74e3f2">
<object name="Id" isNull="false" class="java.lang.String" value="cc74e3f2"/>
</object>
<object isNull="false" class="org.flib.xdstore.entities.XdGalaxy" id="ca519d20">
<object name="Id" isNull="false" class="java.lang.String" value="ca519d20"/>
</object>
</objects>
Таким образом мы получили двух файловое хранилище для наших объектов. Если нам не требуются объекты класса XdGalaxy, то мы можем загрузить лишь объекты класса XdUniverse и работать с ними. Если же нам потребуются объекты класса XdGalaxy, то нам достаточно загрузить их по уже загруженным ссылкам.
В случае, если мы поставим политику хранения объектов SingleObjectFile, в корневом каталоге хранилища будет создана папка, в которую и будут сохраняться файлы объектов.
Сохранение и загрузка объектов
Рассмотрим интерфейс класса XmlDataStore, касающийся операций сохранения объектов. Он довольно прост и позволяет нам сохранять объекты без указания политик, поскольку они уже проставлены при инициализации хранилища.
public class XmlDataStore {
public XmlDataStoreTransaction beginTransaction();
public void commitTransaction(final XmlDataStoreTransaction transaction);
public void rollbackTransaction(final XmlDataStoreTransaction transaction);
public <T extends IXmlDataStoreIdentifiable> boolean saveRoot(final T root) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean saveObject(final T object) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean saveObjects(final Collection<T> objects)
throws XmlDataStoreException
}
Хранилище разрабатывалось для многопоточного использования и в ходе работы может быть задействовано несколько ресурсных объектов, поэтому оно использует механизм транзакций и предоставляет соответствующие методы. Принятие и откат транзакции могут быть вызваны также через методы объекта самой транзакции.
Сохранение корневых объектов и дочерних объектов немного отличаются, поэтому методы работы над корневыми объектами выделены в отдельную группу. Отличие заключается в том, что при политике SingleObjectFile для каждого корневого объекта будет выделен отдельный файл и в дополнение для них всех создан дополнительный файл, в котором будут храниться ссылки. Это позволяет разом загрузить все корневые объекты.
Теперь рассмотрим операцию сохранения.
final XmlDataStore store = initStore("./teststore");
final XdUniverse universe = generateUniverse();
final XmlDataStoreTransaction tx = store.beginTransaction();
try {
store.saveRoot(universe);
store.saveObjects(universe.getGalaxies());
tx.commit();
} catch (XmlDataStoreException e) {
tx.rollback();
}
Из примера видно, что сохранить объекты довольно просто. Отметим лишь тот момент, что поскольку объекты класса XdGalaxy сохраняются в отдельном файле, нам необходимо явно выполнить операцию их сохранения. Их можно также сохранять по отдельности используя другой метод, описанный выше. Сама запись объектов в файл происходит при выполнении операции принятия транзакции и до тех пор пока она не вызвана все операции производятся с кэшем.
Рассмотрим теперь часть интерфейса, относящуюся к загрузке объектов из хранилища.
public class XmlDataStore {
public <T extends IXmlDataStoreIdentifiable> Map<String, T> loadRoots(final Class<T> cl)
throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> T loadRoot(final Class<T> cl, final String id)
throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean loadObject(final T reference) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> T loadObject(Class<T> cl, final String id)
throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean loadObjects(final Collection<T> references)
throws XmlDataStoreException
}
Как видно, хранилище позволяет нам загрузить разом все корни указанного класса или же запросить один корень указанного класса по идентификатору. Также можно загрузить объекты любого класса по ссылке или идентификатору. В нашем случае загрузка всех сохраненных данных будет выглядеть следующим образом.
final XmlDataStore store = initStore("./teststore");
final XmlDataStoreTransaction tx = store.beginTransaction();
try {
final Map<String, XdUniverse> roots = store.loadRoots(XdUniverse.class);
for (final XdUniverse root : roots.values()) {
final Collection<XdGalaxy> galaxies = root.getGalaxies();
store.loadObjects(galaxies);
}
tx.commit();
} catch(XmlDataStoreException e) {
tx.rollback();
}
Из примера видно, что сначала загружаются все корни, а затем для каждого корня по объектным ссылкам загружаются все дочерние объекты.
Обновление и удаление объектов
Методы обновления (изменения) и удаления объектов представлены ниже.
public class XmlDataStore {
public <T extends IXmlDataStoreIdentifiable> boolean updateRoot(final T root) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean deleteRoot(final T root) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean deleteRoot(final Class<T> cl, final String id)
throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean updateObject(final T object) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean deleteObject(final T reference) throws XmlDataStoreException
public <T extends IXmlDataStoreIdentifiable> boolean deleteObjects(final Collection<T> references)
throws XmlDataStoreException
}
Следует отметить, что все зависимые объекты, которые хранятся в отдельных от владельца файлах, должны быть явно обновлены или удалены. Например, в нашем случае при удалении объекта класса XdGalaxy из объекта XdUniverse необходимо обновить объект XdUniverse и дополнительно явно удалить XdGalaxy.
final XmlDataStore store = initStore("./teststore");
final XmlDataStoreTransaction tx = store.beginTransaction();
try {
final Map<String, XdUniverse> roots = store.loadRoots(XdUniverse.class);
for (final XdUniverse root : roots.values()) {
final Collection<XdGalaxy> galaxies = root.getGalaxies();
store.loadObjects(galaxies);
}
if(roots.size() > 0) {
final XdUniverse universe = roots.values().iterator().next();
final XdGalaxy galaxy = universe.removeGalaxy();
if(galaxy != null) {
store.updateRoot(universe);
store.deleteObject(galaxy);
}
}
tx.commit();
} catch(XmlDataStoreException e) {
tx.rollback();
}
В случае добавления объекта код выглядит следующим образом.
final XmlDataStore store = initStore("./teststore");
final XmlDataStoreTransaction tx = store.beginTransaction();
try {
final Map<String, XdUniverse> roots = store.loadRoots(XdUniverse.class);
for (final XdUniverse root : roots.values()) {
final Collection<XdGalaxy> galaxies = root.getGalaxies();
store.loadObjects(galaxies);
}
if(roots.size() > 0) {
final XdUniverse universe = roots.values().iterator().next();
final XdGalaxy galaxy = initGalaxy(); // initialization XdGalaxy
universe.addGalaxy(galaxy);
store.updateRoot(universe);
store.saveObject(galaxy);
}
tx.commit();
} catch(XmlDataStoreException e) {
tx.rollback();
}
Если же политика сохранения ParentObjectFile, то для дочерних объектов нет необходимости явно выполнять операции удаления и сохранения, поскольку после обновления объекта владельца необходимая операция будет выполнена автоматически.
Полная очистка нашего хранилища будет выглядеть следующим образом:
final XmlDataStore store = initStore(storedir);
final XmlDataStoreTransaction tx = store.beginTransaction();
try {
final Map<String, XdUniverse> roots = store.loadRoots(XdUniverse.class);
for (final XdUniverse root : roots.values()) {
final Collection<XdGalaxy> galaxies = root.getGalaxies();
store.deleteObjects(galaxies);
store.deleteRoot(root);
}
tx.commit();
} catch(XmlDataStoreException e) {
tx.rollback();
}
Из примера видно, что нам даже не потребовалось загружать объекты класса XdGalaxy перед удалением. Мы просто передали коллекцию объектных ссылок. Это возможно поскольку объектная ссылка хранит идентификатор объекта.
Немного о реализации
Для повышения производительности работы хранилища используется неотключаемое кэширование. Т.е. при работе с любым ресурсным объектом (файлом) все хранимые в нем объекты загружаются и кэшируются при первой транзакции. Все остальные транзакции работают с уже кэшированными данными. Данные кэша сбрасываются, когда завершается последняя транзакция, которая работает с этим ресурсным объектом. Все изменения регистрируются в кэше и не сбрасываются на диск до тех пор, пока не происходит принятие транзакции.
Поскольку в ходе выполнения транзакции может быть затронуто неопределенное количество ресурсных объектов, то операция принятия изменений транзакции выполняется над всеми поочередно. Если при этом процессе происходит какая-либо ошибка, то целостность хранилища данных нарушается и выбрасывается исключение типа XmlDataStoreRuntimeException. В текущей реализации восстановление целостного состояния хранилища не реализовано. Это один из существенных недостатков текущей версии.
Планы по развитию
В текущей реализации при большом количестве объектов определенного класса и политике хранения ClassObjectsFile, трудоемкость операций чтения и записи растет прямо пропорционально росту количества объектов. Для того чтобы повысить производительность хранилища планируется реализовать фрагментацию и построение файла индекса. Фрагментация подразумевает под собой разбиение одного файла на фрагменты, содержащие ограниченное количество объектов, а индекс в данном случае будет содержать ссылки с указанием файла фрагмента, в котором сохранен объект.
Также в планы входит реализация восстановления целостного состояния хранилища после сбоя при принятии изменений транзакции.
Возможно, что в новой реализации хранилища появятся триггеры, которые будут вызываться при изменении состояния хранимых объектов. Т.е. при добавлении, изменении или удалении объектов.
Автор: Бесчастный Евгений
Много-файловое хранилище Java объектов в формате xml (часть 2)