【原创译文】深入理解Android为什么不允许Room数据库对象间(外键)引用
译者zhangphil@csdn注:当我开始使用Android Room技术时候,Room明确禁止对象间外键引用,让我感觉不可思议,也觉得不正常,因为对象间引用几乎是所有ORM数据库技术非常普遍的设计和理念,并且这对开发者来说无疑很友好很有用,为什么Android官方的Room却禁止了呢?看了这篇谷歌Android官方技术文档我才恍然大悟。这篇文章在Android数据库开发中很有启发意义,因此我特意把它翻译出来以飨读者。
第一,该文档Android官方回答了Android Room禁止对象(外键)引用的原因,但是开发者需要明白,虽然Android Room不允许外键引用,但保留了@Embedded内嵌对象这一设计(见附录文章2),其实通过Room的@Embedded内嵌对象,可以变通的实现外键引用,且性能更佳。
第二,该篇文章更大价值是启发Android开发者在常见的数据库数据访问时候,是如何影响性能以及如何改善APP在加载数据库数据时的性能问题。
第三,开发者在使用常见的ORM数据库如ORMLite过程中,普遍存在对象间(外键)引用,这没什么问题,但是如果对象间引用过程中,非常普通的set/get代码若发生在Android UI主线程,看似一条简单的set/get会严重影响Android性能吗?如果会,又是如何影响呢?本篇技术文档将给出启迪性见解。
以下是我对谷歌Android官方原文的翻译:
关键技术收获:Android Room不允许在不同entity实例对象之间进行(外键、外链)引用。取而代之的是,作为开发者你必须明确的显式请求你自己App需要的数据。
在数据库中各自不同对象之间建立映射引用关系是一种惯常做法并且在服务器端这种技术路线可以良好工作。甚至当程序在它们使用这些变量时候直接存取访问,服务器端仍然性能表现良好。
然而上述按需所取的“懒加载”模式在客户端并不可行,因为这种代码场景发生在Android的UI主线程,在UI主线程就这样发起对硬盘的信息查询将导致明显的性能问题。典型的UI线程要在16毫秒内计算、绘制和更新布局,就算一次数据库查询仅花费5毫秒,也依然可能会把你APP绘制图像帧的时间耗完,引发显而易见的视觉错误。更不幸的是,如果数据库查询事务单独的并行进行,或者此时Android设备也正在进行其他高强度硬盘访问任务,那么将会花费更多时间完成数据查询。如果你不使用懒加载,那么就意味你的APP需要取出更多你不需要的的冗余数据,制造了内存消耗浪费问题。ORM(对象关系映射)通常会把这些决定留给开发者,以让开发者自行决定使用何种最适合的技术路线解决开发者自己APP的情况。开发者通常的策略是在APP和UI里面共享数据库模型Model。此种解决方案未必就好,然而,随着UI的改变,这种共享数据库模型Model引发的问题,开发者很难预知和调试。
举例来说,考虑一种情况:UI要加载一批Book对象,每一个Book对象中引用一个Author 对象。在最初你可能会设计查询策略是使用懒加载Book对象实例,然后使用Book的getAuthor方法返回Author。getAuthor()的第一次调用将触发数据库查询。然后一会,你也认识到你需要在APP的UI里面展示author的名字。你能够很简单的增加这个方法,如下面展示的代码片段:
authorNameTextView.setText(user.getAuthor().getName());
然而,这看似没有问题的代码改变却导致数据库中Author表将会在UI Main Thread主线程被查询。
如果你在此之前先查询author的信息,并把author预先加载出来,这会变得比较困难调整策略如果你却不再使用这些数据。例如,你的APP UI不再需要展示Author 的信息,你的APP却在事实上加载了你不需要的数据,这就浪费了宝贵的内存空间。如果Author 类引用了另外的表,像Book那样,那么这样会导致你的APP性能更加低。
有鉴于此,取而代之的是:在Android Room中,若同一时间内引用多个数据库实体,你应该创建一个POJO,该POJO内嵌每一个你打算使用的实体,此时编写一个查询语句查询需要响应的数据库表。这种良好组织结构的Model,结合Android Room健壮的查询能力,允许你的APP以更低的系统开销加载数据,改进你APP的性能和用户体验。
(正文翻译结束,by zhangphil@csdn)
附录:
1,我翻译的谷歌Android官方原文文档链接:https://developer.android.google.cn/training/data-storage/room/referencing-data.html#understand-no-object-references
2,《Android官方ORM数据库Room技术解决方案:@Embedded内嵌对象(二)》链接:http://blog.csdn.net/zhangphil/article/details/78621009
3,我翻译的谷歌Android官方原文内容:
Understand why Room doesn't allow object references
________________________________________Key takeaway: Room disallows object references between entity classes. Instead, you must explicitly request the data that your app needs.
Mapping relationships from a database to the respective object model is a common practice and works very well on the server side. Even when the program loads fields as they're accessed, the server still performs well.
However, on the client side, this type of lazy loading isn't feasible because it usually occurs on the UI thread, and querying information on disk in the UI thread creates significant performance problems. The UI thread typically has about 16 ms to calculate and draw an activity's updated layout, so even if a query takes only 5 ms, it's still likely that your app will run out of time to draw the frame, causing noticeable visual glitches. The query could take even more time to complete if there's a separate transaction running in parallel, or if the device is running other disk-intensive tasks. If you don't use lazy loading, however, your app fetches more data than it needs, creating memory consumption problems.
Object-relational mappings usually leave this decision to developers so that they can do whatever is best for their app's use cases. Developers usually decide to share the model between their app and the UI. This solution doesn't scale well, however, because as the UI changes over time, the shared model creates problems that are difficult for developers to anticipate and debug.
For example, consider a UI that loads a list of Book objects, with each book having an Author object. You might initially design your queries to use lazy loading such that instances of Book use a getAuthor() method to return the author. The first invocation of the getAuthor() call queries the database. Some time later, you realize that you need to display the author name in your app's UI, as well. You can add the method call easily enough, as shown in the following code snippet:
authorNameTextView.setText(user.getAuthor().getName());
However, this seemingly innocent change causes the Author table to be queried on the main thread.
If you query author information ahead of time, it becomes difficult to change how data is loaded if you no longer need that data. For example, if your app's UI no longer needs to display Author information, your app effectively loads data that it no longer displays, wasting valuable memory space. Your app's efficiency degrades even further if the Author class references another table, such as Books.
To reference multiple entities at the same time using Room, you instead create a POJO that contains each entity, then write a query that joins the corresponding tables. This well-structured model, combined with Room's robust query validation capabilities, allows your app to consume fewer resources when loading data, improving your app's performance and user experience.