Realm 是最好的 Android 数据库解决方案
已发表: 2022-03-11自从 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<? extends RealmObject>
RealmList<? extends RealmObject>
为模型关系。
字段可以具有任何访问级别(私有、公共、受保护等)。 默认情况下,所有字段都是持久化的,您只需要注释“特殊”字段(例如, @PrimaryKey
用于您的主键字段, @Ignore
用于设置非持久化字段等)。
这种方法的有趣之处在于,与 ORM 相比,它使类的“注释污染”更少,因为在大多数情况下,您需要注释来将类映射到表、将常规字段映射到数据库列、将外键字段映射到其他表等等在。
关系
当谈到关系时,有两种选择:
添加一个模型作为另一个模型的字段。 在我们的示例中,
Contact
类包含一个Address
字段并定义了它们之间的关系。 一个联系人可能有一个地址,但没有什么能阻止这个地址被添加到其他联系人中。 这允许一对一和一对多的关系。添加被引用模型的
RealmList
。RealmLists
行为很像老式的 JavaLists
,充当 Realm 对象的容器。 我们可以看到我们的Contact
模型有一个RealmList
联系人,在这个例子中是她的朋友。 可以使用这种方法对一对多和多对多关系进行建模。
我喜欢这种表示关系的方式,因为它对我们 Java 开发人员来说非常自然。 通过将这些对象(或这些对象的列表)直接添加为我们类的字段,就像我们对其他非模型类所做的那样,我们不需要处理外键的 SQLite 设置。
警告:不支持模型继承。 当前的解决方法是使用组合。 因此,例如,如果您有一个Animal
模型并希望创建一个从Animal
扩展的Dog
模型,您将不得不在Dog
中添加一个Animal
实例作为字段。 关于组合与继承的争论很大。 如果您喜欢使用继承,那么这绝对是您需要了解的有关 Realm 的知识。 使用 SQLite,这可以使用通过外键连接的两个表(一个用于父表,一个用于子表)来实现。 一些 ORM 也没有施加此限制,例如 DBFlow。
只检索您需要的数据! 零拷贝设计
这是一个杀手级功能。
Realm 应用了零拷贝设计的概念,这意味着数据永远不会被复制到内存中。 您从查询中获得的结果实际上只是指向真实数据的指针。 数据本身在您访问时会延迟加载。
例如,您有一个包含 10 个字段(SQL 中的列)的模型。 如果您查询此模型的对象以将它们显示在屏幕上,并且您只需要 10 个字段中的三个来填充列表项,那么这些字段将是唯一检索到的字段。
因此,查询速度非常快(有关一些基准测试结果,请参见此处和此处)。
与通常预先从选定 SQL 行加载所有数据的 ORM 相比,这是一个很大的优势。
结果,屏幕加载变得更加高效,无需开发人员进一步努力:这只是 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 架构中的组件,最底层是它的核心,以及平台最基本的实现。 最重要的是,我们将为每个受支持的平台提供绑定库。
当对某些您无法控制的技术使用包装器时,您最终需要围绕它提供某种抽象层。
领域绑定库被设计为尽可能薄,以减少抽象的复杂性。 他们主要从 Core 传播设计理念。 通过控制整个架构,这些组件可以更好地相互同步。
一个实际示例是访问其他引用对象(SQL 中的外键)。 Realm 的文件结构基于本地链接,因此当您查询关系时,不必将 ORM 抽象转换为关系和/或连接多个表,您可以在文件格式的文件系统级别获得指向对象的原始链接。
那是直接指向其他对象的对象。 因此,查询关系与查询整数列是一样的。 不需要遍历外键的昂贵操作。 这都是关于遵循指针的。
社区与支持
Realm 正在积极开发中,并且经常发布更新版本。
Realm 移动数据库中的所有组件都是开源的。 他们对问题跟踪器和 Stack Overflow 的响应非常迅速,因此您可以期待在这些渠道上获得良好和快速的支持。
此外,在确定问题(错误、改进、功能请求等)的优先级时,也会考虑来自社区的反馈。 很高兴知道您可以在您使用的工具的开发中拥有发言权。
我从 2015 年开始使用 Realm,从那时起,我在网上看到了几篇关于 Realm 的不同意见的帖子。 我们将很快讨论它的局限性,但我注意到的一件事是,在发布帖子时提出的许多投诉已经得到解决。
例如,当我了解 Realm 时,尚不支持模型上的自定义方法和异步调用。 这些在当时对许多人来说都是破坏交易的,但目前两者都得到支持。
这样的开发速度和响应能力让我们更有信心,我们不会等待很长时间来等待重要的功能。
限制
与生活中的一切一样,Realm 并不全是玫瑰。 除了前面提到的继承限制之外,还有其他一些缺点需要牢记:
虽然可以有多个线程同时读取和写入数据库,但Realm 对象不能跨线程移动。 因此,例如,如果您使用在后台线程中运行的 AsyncTask 的
doInBackground()
检索领域对象,则无法将此实例传递给onPostExecute()
方法,因为这些方法在主线程上运行。 这种情况的可能解决方法是制作对象的副本并将其传递或传递对象的 id 并在onPostExecute()
上再次检索对象。 Realm 提供了用于读/写的同步和异步方法。不支持自动增量主键,因此您需要自己处理这些的生成。
不可能同时从不同的进程访问数据库。 根据他们的文档,多进程支持即将推出。
Realm 是移动数据库解决方案的未来
SQLite 是一个可靠、健壮且经过验证的数据库引擎,并且关系数据库不会很快消失。 那里有许多好的 ORM,它们也可以在许多场景中发挥作用。
但是,重要的是要及时了解当前趋势。
在这方面,我认为 Realm 是近年来移动数据库开发的最大趋势之一。
Realm 带来了一种独特的方法来处理对开发人员有价值的数据,不仅因为它可以比现有解决方案更好的选择,还因为它在新的可能性方面拓宽了我们的视野并提高了移动数据库技术的标准。
你已经有使用 Realm 的经验了吗? 请随时分享您的想法。