ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android Architecture Components 之 Room 篇,flutter消息推送收费

2022-01-29 13:00:53  阅读:192  来源: 互联网

标签:name 数据库 entity Components Android 查询 public Room



productDao.insertProduct(productEntity);

每个 entity 都代表了一张表,其中的字段代表表中的一列。注解处理器会自动生成 AppDatabaseProductDao 对应的实现类 AppDatabase_ImplProductDao_Impl。可以通过调用Room.databaseBuilder()Room.inMemoryDatabaseBuilder()在运行时获取Database实例,但要注意,实例化 RoomDatabase 是相当昂贵的,最好按照单例模式只创建一个Database实例。

定义 Entity

为了让 Room 可以访问 entity,entity 中的字段必须是 public 的,或者提供了getter/setter方法。默认情况下,Room 会将 entity 中的每个字段作为数据库表中一列,如果你不想持久化某个字段,可以使用 @Ignore 注解。默认数据库表名为 entity 类名,你可以通过 @Entity 注解的 tableName 属性 更改,默认列名是字段名,你可以通过 @ColumnInfo 注解更改。

主键

每个 entity 必须至少有一个字段作为主键(primary key),即使该 entity 只有一个字段。使用 @PrimaryKey 注解来指定主键,如果你希望 SQLite 帮你自动生成这个唯一主键,需要将 @PrimaryKeyautoGenerate 属性设置成 true,不过需要改列是 INTEGER 类型的。如果字段类型是 longintInsert 方法会将 0 作为缺省值,如果字段类型是 IntegerLong 类型,Insert 方法会将 null 作为缺省值。
如果 entity 的主键是复合主键(composite primary key),你就需要使用 @Entity 注解的 primaryKeys 属性定义这个约束,如:

@Entity(primaryKeys = {“firstName”, “lastName”})
class User {
public String firstName;
public String lastName;
public String avatar;
}

索引

有些时候,我们需要添加索引以加快查询速度,可以使用 @![](https://www.hualigs.cn/image/61dba891ed8ee.jpg) Entity 注解的 indices 属性创建索引,如果某个字段或字段组是唯一的,可以将 @Index 注解的 unique 属性设置为 true 来强制这个唯一性,如:

@Entity(indices = {@Index(value = {“first_name”, “last_name”},
unique = true)})
class User {
@PrimaryKey
public int id;

@ColumnInfo(name = “first_name”)
public String firstName;

@ColumnInfo(name = “last_name”)
public String lastName;

@Ignore
Bitmap picture;
}

关系

SQLite 是关系型数据库,很多时候我们需要指定对象间的关系。即使大多数 ORM 库允许实体类对象间相互引用,但 Room 明确禁止这样做。因为级联查询不能发生在 UI 线程,UI 线程只有 16 ms 时间计算和绘制布局,所以即使一个查询只花费 5 ms,你的应用仍可能因此绘制超时,造成明显的视觉问题。而且如果此时还有其他的数据库事务正在运行或者设备正在运行其他磁盘敏感任务,那么该查询将花费更多的时间。而如果你不使用懒加载,你的应用将不得不去获取比所需要的更多的数据,从而产生内存占用问题。
ORM 库通常把这个决定权交给开发者,以便开发者根据自己应用的情况采取措施,而开发者通常会决定在应用和 UI 之间共享 model,然而,这种解决方案并不能很好地扩展,因为随着UI的变化,共享 model 会产生一些难以让开发人员预测和调试的问题。
例如,UI 加载了 Book 对象列表,每个 book 都有一个 Author 对象,你可能最开始想采用懒加载的方式获取 Book实例(使用getAuthor() 方法获取 author),第一次调用 getAuthor() 会调用数据库查询。过一会,你意识到你需要在 UI 上显示作者名,你写了下面这样的代码:

authorNameTextView.setText(user.getAuthor().getName());

这看似正常的变更会导致 Author 表在主线程中被查询。那提前查询好作者信息是不是就行了呢?明显不行,如果你不再需要这些数据,就很难改变数据的加载方式了。例如,如果你的 UI 不再需要显示作者信息了,你的应用仍然会加载这些不需要的数据,从而浪费昂贵的内存空间,如果 Author 又引用了其他表,那么应用的效率将会进一步降低。
所以为了让 Room 能同时引用多个 entity,你需要创建一个包含每个 entity 的 POJO,然后编写一个连接相应表的查询。这个结构良好的 model,结合 Room 健壮的查询校验功能,就能够让你的应用花费更少的资源加载数据,提升应用的性能和用户体验。
虽然不能直接指定对象间关系,但可以指定外键(Foreign Key)约束。例如对于 Book entity 有一个作者的外键引用 User,可以通过 @ForeignKey 注解指定这个外键约束:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = “id”,
childColumns = “user_id”))
class Book {
@PrimaryKey
public int bookId;

public String title;

@ColumnInfo(name = “user_id”)
public int userId;
}

可以通过 @ForeignKey注解的 onDeleteonUpdate 属性指定级联操作,如级联更新和级联删除:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = “id”,
childColumns = “user_id”,
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE))

有时,一个包含嵌套对象的 entity 或 POJO 表示一个完整的数据库逻辑,可以使用 @Embedded 注解将该嵌套对象的字段分解到该表中,如 User 表需要包含 Address相关字段,可以使用 @Embedded 注解表示这是个组合列:

public class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = “post_code”)
public int postCode;
}

@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}

也就是说, User 表包含 idfirstNamestreetstatecity,和 post_code 列。
Embedded 字段也能包含其他 Embedded 字段。
如果有另一个组合列也是 Address 类型的,可以使用 @Embedded 注解的 prefix 属性添加列名前缀以保证列的唯一性。

使用 DAO

DAO(data access objects)是应用中操作数据库的最直接的接口,应用中对数据库的操作都表现在这个对象上,也就是说,应用不需要知道具体的数据库操作方法,只需要利用 DAO 完成数据库操作就行了,所以这一系列 Dao 对象也构成了 Room 的核心组件。DAO 可以是个接口,也可以是个抽象类,如果是个抽象类,那么它可以有个构造器,以 RoomDatabase 作为唯一参数,Room 会在编译时自动生成每个 DAO 的实现类。

新增

定义一个用 @Insert 注解的 DAO 方法,Room 会自动生成一个在单个事务中将所有参数插入数据库的实现,如果方法只有一个参数,那么它可以返回 long 类型的 rowId,如果方法参数是数组或集合,那么它可以返回 long[]List<Long>:

@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User… users);

@Insert
public void insertBothUsers(User user1, User user2);

@Insert
public void insertUsersAndFriends(User user, List friends);
}

更新

@Update 注解的方法可以更改一系列给定的 entity, 使用匹配的主键去查询更改这些 entity,可以返回 int 型的数据库更新行数:

@Dao
public interface MyDao {
@Update
public void updateUsers(User… users);
}

删除

@Delete 注解的方法可以删除一系列给定的 entity, 使用匹配的主键去查询更改这些 entity,可以返回 int 型的数据库删除行数:

@Dao
public interface MyDao {
@Delete
public void deleteUsers(User… users);
}

查询

@Query 注解的方法可以让你方便地读写数据库,Room 会在编译时验证这个方法,所以如果查询有问题编译时就会报错。Room 还会验证查询的返回值,如果查询响应的字段名和返回对象的字段名不匹配,如果有些字段不匹配,你会看到警告,如果所有字段都不匹配,你会看到 error。下面是一个简单的查询,查询所有的用户:

@Dao
public interface MyDao {
@Query(“SELECT * FROM user”)
public User[] loadAllUsers();
}

如果你想要添加查询条件,可以使用 :参数名 的方式获取参数值:

@Dao
public interface MyDao {
@Query(“SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge”)
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search "

  • “OR last_name LIKE :search”)
    public List findUserWithName(String search);
    }

当然,查询条件集合也是支持的:

@Dao
public interface MyDao {
@Query(“SELECT first_name, last_name FROM user WHERE region IN (:regions)”)
public List loadUsersFromRegions(List regions);
}

很多时候,我们不需要查询表中的所有字段,我们只用到了 UI 用到的那几列,为了节省资源,也为了加快查询速度,我们就可以定义一个包含用到的字段的 POJO(这个 POJO 可以使用 @Embedded 注解) ,查询方法可以使用这个 POJO:

public class NameTuple {
@ColumnInfo(name=“first_name”)
public String firstName;

@ColumnInfo(name=“last_name”)
public String lastName;
}

@Dao
public interface MyDao {
@Query(“SELECT first_name, last_name FROM user”)
public List loadFullName();
}

Room 也允许你方便地进行多表查询,如查询某个用户所借的所有书籍信息:

@Dao
public interface MyDao {
@Query("SELECT * FROM book "

  • "INNER JOIN loan ON loan.book_id = book.id "
  • "INNER JOIN user ON user.id = loan.user_id "
  • “WHERE user.name LIKE :userName”)
    public List findBooksBorrowedByNameSync(String userName);
    }

多表查询也能使用 POJO,如查询用户名和他的宠物名:

@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "

  • "FROM user, pet "
  • “WHERE user.id = pet.user_id”)
    public LiveData<List> loadUserAndPetNames();

// You can also define this class in a separate file, as long as you add the
// “public” access modifier.
static class UserPet {
public String userName;
public String petName;
}
}

查询方法的返回值可以是 LiveData 以便你能随着数据库的更新实时更新 UI,返回值也可以是 RxJava2PublisherFlowable(需要添加 android.arch.persistence.room:rxjava2 依赖),甚至可以是 Cursor(不建议直接使用 Cursor API )。

数据库的更新与迁移

随着应用功能的改变,你需要去更改 entity 和数据库,但很多时候,你不希望因此丢失数据库中已存在的的数据,尤其是无法从远程服务器恢复这些数据时。也就是说,如果你不提供必要的迁移操作,Room 将会重建数据库,数据库中所有的数据都将丢失。
为此, Room 允许你写一些 Migration 类去保护用户数据,每个 Migration 类指定一个 startVersionendVersion,在运行时,Room 会运行每个 Migration 类的 migrate() 方法,以正确的顺序将数据库迁移到最新版本:

Room.databaseBuilder(getApplicationContext(), MyDb.class, “database-name”)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Fruit (id INTEGER, "

  • "name TEXT, PRIMARY KEY(id))");
    }
    };

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "

  • " ADD COLUMN pub_year INTEGER");
    }
    };

注意,为了保证迁移逻辑按预期运行,应该使用完整的查询而不是引用表示查询的常量。

在迁移过程完成后,Room 会验证 schema 以确保迁移正确的完成了,如果 Room 发现了问题,会抛出一个包含不匹配信息的异常。
迁移数据库是很重要也是无法避免的操作,如果迁移出错可能会导致你的应用陷入崩溃循环,为了保持应用的稳定性,你必须提前测试好迁移的整的过程。为了更好地测试,你需要添加 android.arch.persistence.room:testing 依赖,并且你需要导出数据库的 schema。在编译时,Room 会将你数据库的 schema 信息导出为 JSON 文件。为了导出 schema,你需要在 build.gradle 文件中设置 注解处理器属性room.schemaLocation:

android {

defaultConfig {

javaCompileOptions {
annotationProcessorOptions {
arguments = [“room.schemaLocation”:
“$projectDir/schemas”.toString()]
}
}
}
}

你需要将这个导出的 JSON 文件保存在版本控制系统中,因为这个文件代表了数据库的 schema 历史记录。同时你需要添加 schema 位置作为 asset 文件夹:

android {

sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}

测试工具中的 MigrationTestHelper 类可以读这些 schema 文件,同时它也实现了 JUnit4 的 TestRule 接口,所以它可以管理创建数据库:

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = “migration-test”;

@Rule
public MigrationTestHelper helper;

public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
MigrationDb.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}

@Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

标签:name,数据库,entity,Components,Android,查询,public,Room
来源: https://blog.csdn.net/m0_66144992/article/details/122741781

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有