Минимальный код для надежного хранения объекта java в файле

в моем крошечном автономном приложении Java я хочу хранить информацию.

мои требования:

  • чтение и запись объектов java (я не хочу использовать SQL, а также запрос не требуется)
  • прост в использовании
  • простота установки
  • минимальные внешние зависимости

поэтому я хочу использовать jaxb для хранения всей информации в простом XML-файл в файловой системе. Мой пример приложение выглядит так (копируйте весь код в файл с именем Application.java и скомпилировать, никаких дополнительных требований!):

@XmlRootElement
class DataStorage {
    String emailAddress;
    List<String> familyMembers;
    // List<Address> addresses;
}

public class Application {

    private static JAXBContext jc;
    private static File storageLocation = new File("data.xml");

    public static void main(String[] args) throws Exception {
        jc = JAXBContext.newInstance(DataStorage.class);

        DataStorage dataStorage = load();

        // the main application will be executed here

        // data manipulation like this:
        dataStorage.emailAddress = "me@example.com";
        dataStorage.familyMembers.add("Mike");

        save(dataStorage);
    }

    protected static DataStorage load() throws JAXBException {
        if (storageLocation.exists()) {
            StreamSource source = new StreamSource(storageLocation);
            return (DataStorage) jc.createUnmarshaller().unmarshal(source);
        }
        return new DataStorage();
    }

    protected static void save(DataStorage dataStorage) throws JAXBException {
        jc.createMarshaller().marshal(dataStorage, storageLocation);
    }
}

Как я могу преодолеть эти недостатки?

  • запуск приложения несколько раз может привести к противоречия: несколько пользователей могут запустить приложение на сетевом диске и испытать параллелизм вопросы
  • прерывание процесса записи может привести к повреждены данные или потеря всех данных

5 ответов


чтобы ответить на ваши три вопроса, Вы сказали:

запуск приложения несколько раз может привести к несоответствиям

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

Если вы хотите сделать его более отказоустойчивым, вы могли бы также поставьте тайм-аут на файл. например, файл блокировки действителен в течение 10 минут. Вы можете написать случайно сгенерированный uuid в lockfile, и перед сохранением вы можете проверить, соответствует ли UUID stil.

несколько пользователей могут запускать приложение на сетевом диске и испытывать проблемы параллелизма

Я думаю, что это то же самое, что и номер 1.

прерывание процесса записи может привести к повреждению данных или потере всех данных

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


видя ваши требования:

  • запуск приложения несколько раз
  • несколько пользователей могут запустить приложение на сетевом диске
  • защита от повреждения данных

Я считаю, что файловой системы на основе XML будет недостаточно. если вы считаете правильную реляционную базу данных излишней, вы все равно можете пойти на H2 db. это супер-легкий DB, который решит все эти проблемы выше (даже если не идеально, но, безусловно, намного лучше, чем рукописный XML-БД), и по-прежнему очень прост в настройке и обслуживании.

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

о "как сохранить данные" детали:

в случае, если вы не хотите использовать любой расширенная библиотека ORM (например, Hibernate или любая другая реализация JPA) вы все еще можете использовать простой старый JDBC. Или хотя бы какой-то Spring-JDBC, который очень легкий и простой в использовании.

"что вы сохраните"

H2-это реляционная база данных. Так что все, что вы сохраните, будет в конечном итоге в колонках. Но! Если вы действительно не планируете запрашивать свои данные (и не применяете к ним сценарии миграции), сохраните уже XML-сериализованные объекты is опции. Вы можно легко определить таблицу с столбцом varchar ID + a "data" и сохранить там xml. В H2DB нет ограничений на длину данных.

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


несоответствия и параллелизм обрабатываются двумя способами:

  • блокировка
  • управление версиями

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

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

все эти функции доступны даже в самой простой реляционной базе данных, например H2, SQLite, и даже веб-страница может использовать такие функции в HTML5. Это довольно перебор, чтобы переопределить их с нуля, и правильная реализация уровня хранения данных на самом деле сделает ваши простые потребности довольно сложными.

но, только для записи:

обработка параллелизма с блокировками

согласованность (атомарность) обработка с замками

  • другие экземпляры приложения могут по-прежнему пытаться прочитать файл, в то время как одно из приложений пишет его. Это может вызвать несогласованность (он же dirty-read). Обеспечить, чтобы во время записи процесс записи имеет эксклюзивную блокировку файла. Если невозможно получить эксклюзивную блокировку доступа, писатель должен немного подождать и повторить попытку.

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

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

основной журнала

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

  • пишет как изменения перейдите в отдельный файл журнала, созданный и заблокированный вашим экземпляром приложения

  • ваш экземпляр приложения не блокирует основной файл, он блокирует только файл журнала

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

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


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

относительно простое решение:

  • использовать отдельный файл для записи данных".XML.lck". заблокируйте это при записи файла
  • как уже упоминалось в мой комментарий, сначала напишите в временный файл " данные.XML.tmp", затем переименуйте в окончательное имя, когда запись будет завершена " данные.XML." это даст разумную гарантию того, что любой, кто читает файл, получит полный файл.
  • даже при блокировке файла вам все равно придется справиться с проблемой "слияния" (один экземпляр читает, другой пишет, затем первый хочет писать). чтобы справиться с этим, у вас должен быть номер версии в содержимом файла. когда экземпляр хочет написать, он сначала получает замок. затем он проверяет номер версии номер версии файла. если он устарел, ему необходимо объединить то, что находится в файле с локальными изменениями. тогда он может написать новую версию.

подумав об этом некоторое время, я хотел бы попытаться реализовать его следующим образом:

  • открыть data.<timestamp>.xml-файл с последней отметкой.
  • использовать только только для чтения режим.
  • внести изменения.
  • сохраните файл как data.<timestamp>.xml - не перезаписывайте и не проверяйте, что файл с более новой меткой времени не существует.