本文概述
自创建Android后, 我们的应用开发人员一直在使用SQLite存储我们的本地数据。有时直接使用SQL语句, 有时使用对象关系映射器(ORM)作为抽象层, 但是无论哪种方式, 我们最终都在使用SQLite。
尽管拥有SQLite的所有优势, 但有时候我们还是希望有关系模型的替代品:某些东西可以使我们不必添加样板代码以在数据库之间来回转换值, 或者使我们能够跳过设置映射在类和表, 字段和列, 外键等之间
换句话说, 数据库的数据结构与我们在应用程序级别实际使用的数据结构更相似。更好的是, 如果通过设计可以提高内存效率, 并在资源受限的设备中提供更好的体验, 那将是非常棒的。
实际上, 这些是我们从Realm获得的一些现成的好处, Realm是具有独特体系结构的数据库平台, 已经成为SQLite的新替代品。
本文介绍了Realm引起如此多关注以及你可能想考虑尝试的一些主要原因。它讨论了Realm通过SQLite向Android开发人员提供的一些关键优势。
由于Realm可在多个平台上使用, 因此本文将介绍的一些内容也与其他移动平台(例如iOS, Xamarin和React Native)相关。
SQLite:可行, 但多数时候并不是你所需要的
大多数移动开发人员可能熟悉SQLite。自2000年以来一直存在, 并且可以说它是世界上使用最广泛的关系数据库引擎。
SQLite有许多我们都认可的好处, 其中之一就是它在Android上的本机支持。
它是标准的SQL关系数据库, 这一事实也使来自关系数据库背景的人的学习曲线最小化。如果充分发挥其潜力, 它还可以提供合理的良好性能(利用功能, 如预准备语句, 具有事务的批量操作等)。尽管SQLite可能无法很好地满足你的所有需求。
但是, 直接处理SQL语句有许多缺点。
根据Android官方文档, 以下是开始读取/写入SQLite所需的步骤:
- 根据合同类别描述你的架构。
- 用字符串定义创建/删除表命令。
- 扩展SQLiteOpenHelper以运行创建命令并管理升级/降级。
完成此操作后, 就可以读取和写入数据库了。但是, 你将需要在应用程序中的对象与数据库中的值之间来回转换。长话短说:这是很多样板代码!
另一个问题是可维护性。随着项目规模的扩大以及对编写更复杂查询的需求的增加, 最终你将获得大量的原始SQL查询字符串。如果以后需要更改这些查询的逻辑, 则可能会很麻烦。
尽管有缺点, 但在某些情况下, 使用原始SQL是最佳选择。一个示例是在开发性能和大小是关键因素的库时, 如果可能, 应避免添加第三方库。
对象关系映射器:SQL挑战的创可贴
为了避免我们处理原始SQL, ORM进行了救援。
一些最著名的Android ORM是DBFlow, greenDAO和OrmLite。
它们带来的最大价值是SQLite抽象, 这使我们相对容易地将数据库实体映射到Java对象。
除其他好处外, 应用程序开发人员还可以使用对象(一种更为熟悉的数据结构)。它也有助于维护, 因为我们现在正在处理具有更强类型的高级对象, 并将脏工作留给库。通过连接字符串或手动处理与数据库的连接, 可以减少构建查询的麻烦。错别字更少。
尽管这些ORM确实提高了Android数据库的标准, 但它们也有其缺点。在许多情况下, 最终会加载不必要的数据。
这是一个例子。
假设你有一个包含15列的表格, 并且在应用程序的某个屏幕中, 将显示该表格中的对象列表。此列表仅显示三列中的值。因此, 通过从表行加载所有数据, 最终将带来比该屏幕实际需要多五倍的数据。
说实话, 在其中一些库中, 你可以指定要先检索的列, 但是为此你需要添加其他代码, 即使这样, 如果你只能确切知道将要检索的列, 这还不够。在查看数据本身之后使用:仍然可能不必要地加载了某些数据。
此外, 在某些情况下, 你需要进行复杂的查询, 而你的ORM库只是无法为你提供一种使用其API描述这些查询的方法。例如, 那会使你编写效率低下的查询, 从而超出你的需要进行更多的计算。
结果是性能下降, 导致你诉诸原始SQL。尽管对于我们许多人而言, 这不是一个大问题, 但它损害了对象关系映射的主要目的, 并使我们回到了上述有关SQLite的问题。
境界:完美的替代品
Realm Mobile Database是一个从头开始为移动设备设计的数据库。
Realm和ORM之间的主要区别在于, Realm不是建立在SQLite之上的抽象, 而是一个全新的数据库引擎。它不是基于关系模型, 而是基于对象存储。它的核心包括一个自包含的C ++库。它目前支持Android, iOS(Objective-C和Swift), Xamarin和React Native。
Realm于2014年6月推出, 因此已经存在两年半了(非常新!)。
自2007年以来, 服务器数据库技术经历了一场革命, 并且出现了许多新技术, 但用于移动设备的数据库技术仍然受SQLite及其包装程序的束缚。这是从头开始创建某些东西的主要动机之一。此外, 正如我们将看到的那样, Realm的某些功能要求对数据库在低层的行为方式进行根本性的改变, 而这根本不可能在SQLite之上构建某些东西。
但是, Realm真的值得吗?这是为什么你应该考虑将Realm添加到工具带的主要原因。
轻松建模
这是使用Realm创建的一些模型的示例:
public class Contact extends RealmObject {
@PrimaryKey
String id;
protected String name;
String email;
@Ignore
public int sessionId;
//Relationships
private Address address;
private RealmList<Contact> friends;
//getters & setter left out for brevity
}
public class Address extends RealmObject {
@PrimaryKey
public Long id;
public String name;
public String address;
public String city;
public String state;
public long phone;
}
你的模型是从RealmObject扩展的。 Realm接受所有原始类型及其装箱的类型(char除外), String, Date和byte []。它还支持RealmObject和RealmList <?的子类。将RealmObject>扩展为模型关系。
字段可以具有任何访问级别(私有, 公共, 受保护等)。默认情况下, 所有字段都是持久性的, 你只需要注释”特殊”字段(例如, 主键字段为@PrimaryKey, @ Ignore设置为非持久字段等)。
这种方法的有趣之处在于, 与ORM相比, 它使类减少了”污染的注释”, 因为在大多数情况下, 你需要注释以将类映射到表, 将常规字段映射到数据库列, 将外键字段映射到其他表等等。上。
人际关系
关于关系, 有两种选择:
-
将模型添加为另一个模型的字段。在我们的示例中, Contact类包含一个Address字段并定义了它们之间的关系。一个联系人可能有一个地址, 但是没有什么可以阻止将该地址添加到其他联系人中。这允许一对一和一对多的关系。
-
添加要引用的模型的RealmList。 RealmLists的行为就像旧的Java列表一样, 充当Realm对象的容器。我们可以看到, 我们的Contact模型具有一个RealmList联系人, 在此示例中是她的朋友。可以使用这种方法对一对多和多对多关系进行建模。
我喜欢这种表示关系的方式, 因为它对我们Java开发人员来说非常自然。通过将这些对象(或这些对象的列表)直接添加为类的字段, 就像我们对其他非模型类所做的那样, 我们不需要处理外键的SQLite设置。
注意:不支持模型继承。当前的解决方法是使用合成。因此, 例如, 如果你有一个Animal模型, 并希望创建一个从Animal扩展的Dog模型, 那么你将不得不添加Animal实例作为Dog中的字段。关于合成与继承有很大的争论。如果你要使用继承, 那么这绝对是你需要了解的Realm知识。使用SQLite, 可以使用通过外键连接的两个表(一个用于父级, 一个用于子级)来实现。某些ORM也没有施加此限制, 例如DBFlow。
仅检索你需要的数据!零拷贝设计
这是一个杀手级功能。
Realm采用零复制设计的概念, 这意味着数据永远不会复制到内存中。你从查询中获得的结果实际上只是指向实际数据的指针。数据本身在你访问时会延迟加载。
例如, 你有一个包含10个字段的模型(SQL中的列)。如果查询此模型的对象以将其显示在屏幕上, 并且只需要10个字段中的3个来填充列表项, 则将是检索到的唯一字段。
结果, 查询速度很快(有关基准测试结果, 请参见此处和此处)。
与ORM相比, 这是一个很大的优势, ORM通常会从预先选择的SQL行中加载所有数据。
这样一来, 屏幕加载将变得更加高效, 而无需开发人员进一步努力:这只是Realm的默认行为。
此外, 这也意味着应用程序占用的内存更少, 并且考虑到我们在谈论的是资源受限的环境(例如移动设备), 这可能会带来很大的不同。
零复制方法的另一个结果是, Realm管理的对象会自动更新。
数据永远不会复制到内存中。如果你有查询的结果, 并且查询之后另一个线程已在数据库上更新了此数据, 则你拥有的结果将已经反映了这些更改。你的结果只是指向实际数据的指针。因此, 当你从字段访问值时, 将返回最新数据。
例如, 如果你已经从Realm对象中读取数据并将其显示在屏幕上, 并且想要在基础数据发生更改时接收更新, 则可以添加一个侦听器:
final RealmResults<Contact> johns = realm.where(Contact.class).beginsWith("name", "John ").findAll();
johns.addChangeListener(new RealmChangeListener<RealmResults<Contact>>() {
@Override
public void onChange(RealmResults<Contact> results) {
// UPDATE UI
}
});
不只是包装
尽管我们为ORM提供了许多选择, 但它们都是包装器, 而所有这些都归结为SQLite的底层, 这限制了它们的使用范围。相反, Realm不仅仅是另一个SQLite包装器。它具有提供ORM无法提供的功能的自由。
Realm的基本变化之一是能够将数据存储为对象图存储。
这意味着Realm是从编程语言级别到数据库的所有对象。因此, 与关系数据库相比, 在读写值时来回转换的方式更少。
数据库结构更紧密地反映了应用程序开发人员使用的数据结构。实际上, 这是为什么在服务器端开发中从关系建模转向聚合模型的主要原因之一。 Realm最终将其中一些想法带入了移动开发世界。
如果我们考虑Realm体系结构中的组件, 则最底层是平台最核心的实现核心。最重要的是, 我们将为每个受支持的平台提供绑定库。
当对某些技术使用包装器时, 你无法控制它, 最终需要在其周围提供某种抽象层。
Realm绑定库被设计为尽可能薄, 以减少抽象的复杂性。他们大多传播了Core的设计思想。通过控制整个体系结构, 这些组件之间可以更好地同步。
一个实际的例子是对其他引用对象(SQL中的外键)的访问。 Realm的文件结构基于本机链接, 因此, 当你查询关系时, 不必将ORM抽象转换为关系表和/或联接多个表, 而是可以文件格式在文件系统级别获得指向对象的原始链接。
这些对象直接指向其他对象。因此, 例如, 查询关系与查询整数列相同。无需进行遍历外键的昂贵操作。这都是关于跟随指针的。
社区与支持
Realm正在积极开发中, 并且经常发布更新版本。
Realm Mobile数据库中的所有组件都是开源的。他们对问题跟踪和堆栈溢出反应非常快, 因此你可以期望在这些渠道上获得良好而快速的支持。
同样, 在对问题(错误, 改进, 功能请求等)进行优先级排序时, 也会考虑来自社区的反馈。很高兴知道你可以在使用的工具的开发方面拥有发言权。
我从2015年开始使用Realm, 从那时起, 我在网上发布了几篇关于Realm的各种观点。我们将很快讨论其局限性, 但是我注意到的一件事是, 自发帖之日起, 许多投诉已得到解决。
例如, 当我了解Realm时, 还不支持模型和异步调用上的自定义方法。当时很多时候, 这些都是破坏交易的因素, 但目前都支持。
这样的开发速度和响应能力使我们更加自信, 我们不会等很久了。
局限性
就像生活中的一切一样, Realm并非全都是玫瑰。除了前面提到的继承限制外, 还有其他缺点需要牢记:
-
尽管可以同时有多个线程读写数据库, 但是Realm对象不能跨线程移动。因此, 例如, 如果你使用AsyncTask的doInBackground()来检索Realm对象, 该对象在后台线程中运行, 则你无法将此实例传递给onPostExecute()方法, 因为这些实例是在主线程上运行的。在这种情况下, 可能的解决方法是制作对象的副本并将其传递, 或传递对象的ID并在onPostExecute()上再次检索该对象。 Realm提供用于读取/写入的同步和异步方法。
-
不支持自动递增主键, 因此你需要自己处理这些主键。
-
无法同时从不同的进程访问数据库。根据他们的文档, 多进程支持即将推出。
Realm是移动数据库解决方案的未来
SQLite是一个可靠, 强大且经过验证的数据库引擎, 关系数据库不会很快消失。那里有许多好的ORM, 在许多情况下也可以解决问题。
但是, 重要的是要保持最新趋势。
在这方面, 我认为在移动数据库开发方面, Realm是近年来最大的趋势之一。
Realm带来了一种独特的方法来处理对开发人员有价值的数据, 不仅因为它是比现有解决方案更好的选择, 而且还因为它拓宽了我们在新可能性方面的视野, 并提高了移动数据库技术的门槛。
你已经拥有Realm的经验吗?请随时分享你的想法。