将java对象可靠地存储在文件中的最小代码

时间:2016-02-18 10:43:01

标签: java xml jaxb file-storage

在我的小型独立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 个答案:

答案 0 :(得分:7)

查看您的要求:

  • 多次启动应用程序
  • 多个用户可以在网络驱动器上运行该应用程序
  • 防止数据损坏

我认为基于XML的文件系统是不够的。 如果你认为一个适当的关系数据库是一个过度杀手,你仍然可以选择H2 db这是一个超轻量级的数据库,它可以解决上述所有这些问题(即使不完美,但是肯定比手写的XML数据库好得多,并且仍然很容易设置和维护。

您可以将其配置为将更改保留到磁盘,可以配置为作为独立服务器运行并接受多个连接,也可以作为嵌入式模式的应用程序的一部分运行。

关于&#34;如何保存数据&#34; 部分:

如果您不想使用任何高级ORM库(如Hibernate或任何其他JPA实现),您仍然可以使用普通的旧JDBC。或者至少有一些Spring-JDBC,它非常轻巧且易于使用。

&#34;你节省了什么&#34;

H2是一个关系数据库。所以无论你保存什么,它都会以列的形式出现。但!如果您确实不打算查询数据(既不在其上应用迁移脚本),则保存已经XML序列化的对象 是一个选项。您可以轻松定义一个ID + a&#34;数据&#34; varchar列,并保存您的xml。 H2DB中的数据长度没有限制。

注意:在关系数据库中保存XML通常不是一个好主意。我只建议您评估此选项,因为您似乎确信您只需要SQL实现可以提供的一组特定功能。

答案 1 :(得分:3)

不一致性和并发性有两种处理方式:

  • 通过锁定
  • 通过版本控制

在应用程序级别无法很好地处理损坏的写入。文件系统应支持日志,它试图在某种程度上修复它。您也可以通过

执行此操作
  • 制作您自己的日记文件(即包含更改的短期单独文件,以提交给真实数据文件)。

即使在最简单的关系数据库中也可以使用所有这些功能,例如: H2,SQLite甚至网页都可以在HTML5中使用这些功能。从头开始重新实现这些实在是太过分了,数据存储层的正确实现实际上会使您的简单需求变得非常复杂。

但是,仅仅是为了记录:

使用锁的并发处理

使用锁

处理一致性(原子性)
  • 其他应用程序实例可能仍会尝试读取该文件,而其中一个应用程序正在编写该文件。这可能导致不一致(又称脏读)。确保在写入期间,编写器进程对文件具有独占锁定。如果无法获得独占访问锁定,则必须等待一段时间再重试。

  • 读取文件的应用程序应读取它(如果它可以获得访问权限,其他实例不执行独占锁定),然后关闭该文件。如果无法读取(因为其他应用程序锁定),请等待并重试。

  • 仍然是外部应用程序(例如记事本)可以更改xml。阅读文件时,您可能更喜欢独占读锁。

基本日记

这里的想法是,如果您可能需要执行大量写操作(或者如果您以后可能想要回滚写入),则不希望触及真实文件。代替:

  • 写入更改为单独的日记文件,由您的应用实例创建并锁定

  • 您的应用实例未锁定主文件,它仅锁定日记文件

  • 一旦完成所有写入操作,您的应用程序将打开具有独占写入锁定的真实文件,并提交日记文件中的每个更改,然后关闭该文件。

正如您所看到的,带锁的解决方案使文件成为共享资源,受锁的保护,一次只有一个应用程序可以访问该文件。这解决了并发问题,但也使文件访问成为瓶颈。因此,现代数据库(如Oracle)使用版本控制而不是锁定。版本控制意味着文件的旧版本和新版本同时可用。读者将由旧的,最完整的文件提供服务。完成新版本的编写后,它将合并到旧版本,新数据将立即可用。这实现起来比较棘手,但由于它允许并行读取所有应用程序,因此它可以更好地扩展。

答案 2 :(得分:2)

请注意,您的简单答案不会处理不同实例的并发写入。如果两个实例进行更改并保存,只需选择最新的实例将最终丢失其他实例的更改。正如其他答案所提到的,你应该尝试使用文件锁定。

一个相对简单的解决方案:

  • 使用单独的锁定文件来编写&#34; data.xml.lck&#34;。在编写文件时将其锁定
  • 如我的评论中所述,首先写入临时文件&#34; data.xml.tmp&#34;,然后在写完成时重命名为最终名称&#34; data.xml&#34;。这样可以合理地保证读取文件的人都能获得完整的文件。
  • 即使文件锁定,您仍然需要处理&#34;合并&#34;问题(一个实例读,另一个写,然后第一个想写)。为了处理这个问题,你应该在文件内容中有一个版本号。当一个实例想要写时,它首先获取锁。然后它根据文件版本号检查其本地版本号。如果它已过期,则需要将文件中的内容与本地更改合并。那么它可以写一个新版本。

答案 3 :(得分:2)

回答你提到的三个问题:

多次启动应用程序可能会导致不一致

为什么会导致不一致?如果你的意思是多个并发编辑会导致不一致,你只需要在编辑之前锁定文件。在文件旁边创建锁文件的最简单方法。在开始编辑之前,只需检查是否存在锁定文件。

如果要使其更具容错能力,还可以对文件设置超时。例如锁定文件有效期为10分钟。你可以在lockfile中编写一个随机生成的uuid,在保存之前,你可以检查uuid stil是否匹配。

多个用户可以在网络驱动器上运行该应用程序并遇到并发问题

我认为这与1号相同。

中止写入过程可能会导致数据损坏或丢失所有数据

这可以通过使write atomic或文件不可变来解决。要使其成为原子,而不是直接编辑文件,只需复制文件,然后在副本上进行编辑。保存副本后,只需重命名文件。但是如果你想要更安全的一面,你总是可以做一些事情,比如在文件上附加时间戳,永远不要编辑或删除文件。因此,每次进行编辑时,都会创建一个副本,并在文件上附加一个较新的时间戳。阅读时,你会读到最新的一本。

答案 4 :(得分:0)

在考虑了一段时间之后,我想尝试像这样实现它:

  • 使用最新时间戳打开data.<timestamp>.xml - 文件。
  • 仅使用只读模式。
  • 进行更改。
  • 将文件另存为data.<timestamp>.xml - 请勿覆盖并检查是否存在没有更新时间戳的文件。