Android Room Orm框架学习

作者: CyrusChan | 来源:发表于2017-06-27 10:07 被阅读12133次

    原文地址

    Room持久化库

    Room为SQLite提供一个抽象层,在充分利用SQLite的同时,允许流畅的数据库访问

    注意:引入Room到你的android工程,参看 adding components to your project

    应用处理大量的结构化数据能够从本地持久化数据获益很多,最通用的例子是缓存相关的数据碎片。那样,当设备不能访问网络的时候,用户仍然可以浏览内容。任何用户发起的内容改变在设备恢复网络的时候同步到服务器上。

    核心框架对raw SQL内容提供嵌入支持。尽管这些APIs是很给力的,但是他们相当低级并且需要大量的时间和精力去使用:

    • raw SQL查询没有编译时验证。当你的数据图改变,你需要手动的更新受影响的SQL查询。这个过程是耗时的和容易出错的。
    • 你需要使用大量的样板代码在数据查询和java数据对象之间转换

    Room为你处理这些问题。在Room中有三个主要组件。

    • Database: 你可以使用这个组件创建一个数据库holder。注解定义了一系列entities并且类的内容提供了一系列DAOs,它也是下层的主要连接 的访问点。
      注解的类应该是一个抽象的继承 RoomDatabase的类。在运行时,你能获得一个实例通过调用Room.databaseBuilder()或者 Room.inMemoryDatabaseBuilder()
    • Entity:这个组件代表了一个持有数据行的类。对于每个entity,一个数据库表被创建用于持有items。你必须引用entity类通过Database类中的entities数组。每个entity字段被持久化到数据库中除非你注解它通过@Ignore.

    注意:Entities能够有一个空的构造函数(如果dao类能够访问每个持久化的字段)或者一个参数带有匹配entity中的字段的类型和名称的构造函数,例如一个只接收其中一些字段的构造函数。

    • DAO:这个组件代表了一个类或者接口作为DAO。DAOs 是Room中的主要组件,并且负责定义访问数据库的方法。被注解为@Database的类必须包含一个没有参数的抽象方法并且返回注解为@Dao的类。当在编译时生成代码,Room创建一个这个类的实现。

    注意:使用DAO类访问数据库而不是query builders或者直接查询。你可以把数据库分成几个组件。还有,DAOs允许你轻松的模拟数据库访问当你测试你的应用的时候。

    这些组件和rest app的关系,如图1.

    Paste_Image.png

    图1:room 架构图
    如下代码片段包含一个数据库配置的例子、一个entity,一个DAO:
    User.java

    @Entity
    public class User {
        @PrimaryKey
        private int uid;
    
        @ColumnInfo(name = "first_name")
        private String firstName;
    
        @ColumnInfo(name = "last_name")
        private String lastName;
    
        // Getters and setters are ignored for brevity,
        // but they're required for Room to work.
    }
    

    UserDao.java

    @Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();
    
        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);
    
        @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
               + "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);
    
        @Insert
        void insertAll(User... users);
    
        @Delete
        void delete(User user);
    }
    

    AppDatabase.java

    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    

    通过创建以上文件,你可以使用如下代码创建一个数据库实例:

    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();
    

    注意:你必须遵守单例模式当初始化一个AppDatabase对象,因为每个RoomDatabase实例是相当昂贵的,并且你几乎不需要访问多个实例。

    Entities

    当一个类被注解为@Entity并且引用到带有@Database 注解的entities属性,Room为这个数据库做的entity创建一个数据表。
    默认情况下,Room为每个定义在entity中的字段创建一个列。如果一个entity的一些字段你不想持久化,你可以使用@Ignore注解它们,像如下展示的代码片段:

    @Entity
    class User {
        @PrimaryKey
        public int id;
    
        public String firstName;
        public String lastName;
    
        @Ignore
        Bitmap picture;
    }
    

    为了持久化一个字段,Room必须有它的入口。你可以使字段为public,或者你可以提供一个setter或者getter。如果你使用setter或者getter方法,记住在Room中他们遵守Java Beans的惯例。

    Primary Key(主键)

    每个entity必须定义至少一个字段作为主键。即使这里只有一个字段,你仍然需要使用@PrimaryKey注解这个字段。并且,如果你想Room动态给entities分配IDs,你可以设置@PrimaryKey’s 的autoGenerate属性。如果entity有个组合的主键,你可以使用@Entity注解的primaryKeys的属性,正如如下片段展示的那样。

    @Entity(primaryKeys = {"firstName", "lastName"})
    class User {
        public String firstName;
        public String lastName;
    
        @Ignore
        Bitmap picture;
    }
    

    默认情况下,Room使用类名作为数据库的表名。如果你希望表有一个不同的名称,设置@Entity注解的tableName属性,如下所示:

    @Entity(tableName = "users")
    class User {
        ...
    }
    

    注意:SQLite中的表名是大小写敏感的。

    与tablename属性相似的是,Room使用字段名称作为列名称。如果你希望一个列有不同的名称,为字段增加@ColumnInfo注解,如下所示:

    @Entity(tableName = "users")
    class User {
        @PrimaryKey
        public int id;
    
        @ColumnInfo(name = "first_name")
        public String firstName;
    
        @ColumnInfo(name = "last_name")
        public String lastName;
    
        @Ignore
        Bitmap picture;
    }
    

    Indices and uniqueness(索引和唯一性)

    根据你访问数据的方式,你可能希望索引确切的字段去加速你的数据库查询。为了给一个entity增加索引。
    在@Entity属性中包含indices属性,在索引或者组合索引中列出你希望包含的列的名称。如下代码片段说明了这个注解过程:

    @Entity(indices = {@Index("name"), @Index("last_name", "address")})
    class User {
        @PrimaryKey
        public int id;
    
        public String firstName;
        public String address;
    
        @ColumnInfo(name = "last_name")
        public String lastName;
    
        @Ignore
        Bitmap picture;
    }
    

    有时,确切的字段和组字段必须是独一无二的。你可以强加这个独一无二的特性通过设置一个@Index注解的unique属性为true。如下代码阻止了表拥有两行包含同样的firstName和last列的值集合。

    @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;
    }
    

    Relationships

    因为SQLite是个关系型数据库,你能够指明两个对象的关系。虽然大多数ORM库支持entity对象引用其他的。Room明确的禁止这样。更多细节请参考 Addendum: No object references between entities.

    即使你不能使用直接关系,Room仍然允许你定义外键约束在两个entities中。

    例如:如果有一个entity叫book,你可以定义它和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;
    }
    

    外键是十分强大的,因为它们允许你指明当引用的entity被更新后做什么。例如,你可以让SQLite为一个user删除所有的书籍如果相应的user实例被删除了通过包含@ForeignKey注解的onDelete=CASCADE属性

    注意:SQLite处理@Insert(OnConflict=REPLACE) 作为一个REMOVE和REPLACE操作而不是单独的UPDATE操作。这个替换冲突值的方法能够影响你的外键约束。更多细节,参看 SQLite documentation

    Nested objects

    有时,你希望entity或者POJOs作为一个整体在你数据库的逻辑当中,即使对象包含几个字段。在这种情况下,你可以使用@Embedded注解去代表一个你希望分解成一个表中的次级字段的对象。接着你就可以查询嵌入字段就像其他单独的字段那样。

    例如,我们的user类能够包含一个代表了street,city,state,postCode的组合字段Address。为了分别的保存组合列,包括被@Embedded注解的user类中的Address字段,如下所示:

    class Address {
        public String street;
        public String state;
        public String city;
    
        @ColumnInfo(name = "post_code")
        public int postCode;
    }
    
    @Entity
    class User {
        @PrimaryKey
        public int id;
    
        public String firstName;
    
        @Embedded
        public Address address;
    }
    

    Table表示了一个包含如下名称列的User对象:id,firstName,street,state,city和post_code。

    注意:嵌入字段也包括其他嵌入字段

    如果一个字段有多个同一类型的嵌入字段,你能保持每个列是独一无二的通过设置prefix属性。Room然后将所提供的值添加到嵌入对象中每个列名的开头

    Data Access Objects (DAOs)

    Room中的主要组件是Dao类。DAOs抽象地以一种干净的方式去访问数据库。

    注意:Room不允许在主线程中访问数据库除非你在建造器中调用allowMainThreadQueries(),因为它可能长时间的锁住UI。异步查询(返回LiveData或者RxJava流的查询)是从这个规则中豁免的因为它们异步的在后台线程中进行查询。

    Methods for convenience(惯例方法)

    这里有很多你可表示的查询惯例使用DAO类。这篇文档包括几个通用的例子:

    Insert

    当你创建一个DAO方法并且使用@Insert注解它,Room生成一个在单独事务中插入所有参数到数据库中的实现。
    如下代码展示了几个查询实例:

    @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<User> friends);
    }
    

    如果@Insert方法接收只有一个参数,它可以返回一个插入item的新rowId 的long值,如果参数是一个集合的数组,它应该返回long[]或者List<Long>

    更多细节,参看文档 @Insert
    注解,和 SQLite documentation for rowid tables

    Update

    Update 是更新一系列entities集合、给定参数的惯例方法。它使用query来匹配每个entity的主键。如下代码说明如何定义这个方法:

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

    尽管通常不是必须的,你能够拥有这个方法返回int值指示数据库中更新的数量。

    Delete

    Delete是一个从数据库中删除一系列给定参数的entities的惯例方法。它使用主键找到要删除的entities。如下所示:

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

    尽管通常不是必须的,你能够拥有这个方法返回int值指示数据库中删除的数量。

    Methods using @Query

    @query 是DAO类中使用的主要注解,它允许你执行读/写操作在数据库中。每个@Query方法在编译时被校验,所以如果查询出了问题,将在编译时出现而不是运行时。

    • 它给出警告如果仅有一些字段匹配
    • 它报错如果没有字段匹配

    查询示例:

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

    这是载入所有用户的非常简单的查询例子。在编译时,Room知道这是查询user表中的所有列。如果查询包含语法错误,或者如果用户表不存在,Room在你app编译时会报出合适的错误消息。

    往查询中传入参数:
    大多数时间,你需要传入参数到查询中去过滤操作,例如只展示比一个特定年龄大的用户,为了完成这个任务,在你的Room注解中使用方法参数,如下所示:

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        public User[] loadAllUsersOlderThan(int minAge);
    }
    

    当这个查询在编译器被处理,Room匹配:minAge绑定的方法参数。Room执行匹配通过使用参数名称,如果没有匹配到,在你的app编译期将会报错。
    你也可以通过传入多个参数或者多次引用它们在一个查询当中,如下所示:

    @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<User> findUserWithName(String search);
    }
    

    Returning subsets of columns(返回列中的子集)

    多数时候,你仅仅需要获取一个entity中的部分字段。例如,你的UI可能只展示user’s第一个和最后一个名称,而不是所有关于用户的细节。你保存有价值的资源通过获取展示在你app’s的UI的列,你的查询完成的更快。
    Room允许你返回任何java对象从查询中只要列结果集能够被映射到返回的对象中。例如:
    你能够创建如下POJO通过拿取用户的姓和名。

    public class NameTuple {
        @ColumnInfo(name="first_name")
        public String firstName;
    
        @ColumnInfo(name="last_name")
        public String lastName;
    }
    

    现在,你可以使用这个POJO在你的查询方法中:

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }
    

    Room理解查询返回first_name和last_name的列值并且这些值被映射到NameTuple类的字段中。因此,Room能够生成合适的代码。如果查询返回太多columns,或者一个列不存在,Room将会报警。

    注意:这些POJOs也使用@Embedded注解

    Passing a collection of arguments

    你的部分查询可能需要你传入可变数量的参数,确切数量的参数直到运行时才知道。例如,你可能想提取来自某个地区所有用户的信息。Room理解当一个参数代表一个集合并且自动的在运行时扩展它根据提供的参数数量。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }
    

    Observable queries

    你经常希望你的app’sUI自动更新当数据发生改变。为了实现这点,使用返回值类型为liveData在你的查询方法描述中。Room生成所有需要的代码去更新LiveData当数据库被更新。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }
    

    注意:在1.0版本,Room使用被访问的table列表在查询中决定是否更新数据对象。

    RxJava

    Room也能返回RxJava2 Publisher和Flowable对象从你定义的查询当中。为了使用这个功能,添加android.arch.persistence.room:rxjava2 到你的build Gradle依赖。你能够返回Rxjava2定义的对象,如下所示:

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);
    }
    

    Direct cursor access(直接游标访问)

    如果你的应用逻辑直接访问返回的行,你可以返回一个Cursor对象从你的查询当中,如下所示:

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        public Cursor loadRawUsersOlderThan(int minAge);
    }
    

    注意:非常不建议使用Cursor API 因为它不能保证行是否存在或者行包含什么值。使用这个功能仅仅是因为你已经有期望返回一个cursor的代码并且你不能轻易的重构。

    Querying multiple tables

    你的一些查询可能访问多个表去计算结果。Room允许你写任何查询,所以你也能连接表格。还有,如果答复是一个observable数据类型,例如Flowable或者LiveData,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<Book> findBooksBorrowedByNameSync(String userName);
    }
    

    你也能返回POJOs从这些查询当中,例如,你可以写一个查询去装载user和他们的宠物名称,如下:

    @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<UserPet>> 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;
       }
    }
    

    Using type converters (使用类型转换)

    Room为原始类型和可选的装箱类型提供嵌入支持。然而,有时你可能使用一个单独存入数据库的自定义数据类型。为了添加这种类型的支持,你可以提供一个把自定义类转化为一个Room能够持久化的已知类型的TypeConverter。
    例如:如果我们想持久化日期的实例,我们可以写如下TypeConverter去存储相等的Unix时间戳在数据库中:

    public class Converters {
        @TypeConverter
        public static Date fromTimestamp(Long value) {
            return value == null ? null : new Date(value);
        }
    
        @TypeConverter
        public static Long dateToTimestamp(Date date) {
            return date == null ? null : date.getTime();
        }
    }
    

    之前的例子定义了两个函数,一个把Date对象转换为Long对象。另一个逆向转换,从Long到Date。因为Room已经知道了如何持久化Long对象,它能使用转换器持久化Date类型。
    接着,你增加@TypeConverters注解到AppDatabase类为了Room能够使用你已经为每个entity定义的转换器和DAO
    AppDatabase.java

    @Database(entities = {User.java}, version = 1)
    @TypeConverters({Converter.class})
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }
    

    使用这些转换器,你可以使用你自定义类型在其他查询中,就像你使用的原始类型,如下代码片段所示:
    User.java

    @Entity
    public class User {
        ...
        private Date birthday;
    }
    UserDao.java
    @Dao
    public interface UserDao {
        ...
        @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
        List<User> findUsersBornBetweenDates(Date from, Date to);
    }
    

    您还可以将@typeconverter限制在不同的范围内,包含单独的entities,DAOs,和DAO methods。更多细节,请参考 @TypeConverters
    文档

    Database migration

    当你添加或改变你app的特性,你需要修改你的entity类去反映这些改变。当一个用户更新你应用到最近的版本,你不希望他们丢失已经存在的数据,特别是你无法从远程服务器恢复数据。
    Room允许你使用Migration类保留用户数据以这种方式。每个Migration类在运行时指明一个开始版本和一个结束版本,Room执行每个Migration类的migrate()方法,使用正确的顺序去迁移数据库到一个最近版本。

    注意:如果你不提供必需的migrations类,Room重建数据库,也就意味你将丢失数据库中的所有数据。

    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发现问题,它将抛出不匹配异常。

    Testing migrations

    迁移并不是一件简单的事情,如果不能正确编写将会造成应用崩溃。为了保证你应用的稳定性,你应该在提交前测试你的迁移类。Room提供一个测试Maven组件去协助测试过程。然而,为了让这个组件工作,你需要到处你的数据库schema。

    Exporting schemas

    根据编译,Room导出你的数据库Schema到一个JSON文件中。为了导出schema,设置 注释处理器的属性room.schemaLocation在你的build.gradle文件中,如下所示:
    build.gradle

    android {
        ...
        defaultConfig {
            ...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
                }
            }
        }
    }
    

    你应该存储导出的JSON文件-代表了你数据库schema的历史-在你的版本控制系统中,正如它允许创建老版本的数据库去测试。

    为了测试这些migrations,添加 android.arch.persistence.room:testing Maven artifac从Room当中到你的测试依赖当中,并且把schema 位置当做一个asset文件添加,如下所示:
    build.gradle

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

    测试package提供一个 可以读取这些schema文件的MigrationTestHelper类。它也是Junit4 TestRule类,所以它能管理创建的数据库。
    如下代码展示了一个测试migration的例子:

    @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);
    
            // db has schema version 1. insert some data using SQL queries.
            // You cannot use DAO classes because they expect the latest schema.
            db.execSQL(...);
    
            // Prepare for the next version.
            db.close();
    
            // Re-open the database with version 2 and provide
            // MIGRATION_1_2 as the migration process.
            db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
    
            // MigrationTestHelper automatically verifies the schema changes,
            // but you need to validate that the data was migrated properly.
        }
    }
    

    Testing your database

    当运行你app的测试时,你不应该创建一个完全的数据库如果你不测试数据库本身。Room允许你轻松的模仿数据访问层在测试当中。这个过程是可能的因为你的DAOs不暴漏任何你数据库的细节。当测试你的应用,你应该创建模仿你的DAO类的假的实例。
    这儿有两种方式去测试你的数据库:

    • 在你的开发主机上
    • 在一个Android设备上

    Testing on your host machine

    Room使用SQLite支持库,这个支持库提供匹配这些Android Framework类的接口并且允许你通过自定义支持库实现去测试你的数据库查询。
    即使这个装置允许你的测试运行很快,它是不建议的因为用户设备的SQLite版本和可能与host主机不匹配。

    Testing on an Android device

    测试你的数据库推荐的方法实现是写一个单元测试在Android设备上。因为这些测试不需要创建一个activity,他讲bicentennialUI单元测试快。
    当装置你的测试用例时,你应该创建一个数据库的内存版本好让你的测试更密闭,如下所示:

    @RunWith(AndroidJUnit4.class)
    public class SimpleEntityReadWriteTest {
        private UserDao mUserDao;
        private TestDatabase mDb;
    
        @Before
        public void createDb() {
            Context context = InstrumentationRegistry.getTargetContext();
            mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
            mUserDao = mDb.getUserDao();
        }
    
        @After
        public void closeDb() throws IOException {
            mDb.close();
        }
    
        @Test
        public void writeUserAndReadInList() throws Exception {
            User user = TestUtil.createUser(3);
            user.setName("george");
            mUserDao.insert(user);
            List<User> byName = mUserDao.findUsersByName("george");
            assertThat(byName.get(0), equalTo(user));
        }
    }
    

    更多关于测试数据库migrations的信息参看 Migration Testing

    相关文章

      网友评论

      • 案上春秋:entity中如果有List<Book> 或者 Book[] books怎么标记
        案上春秋:@CyrusChan ok
        CyrusChan:虽然大多数ORM库支持entity对象引用其他的。Room明确的禁止这样。

        我在google官方文档上找的一个例子是这样的,你看下对你有没有参考意义。
        @Entity
        public class Pet {
        @ PrimaryKey
        int id;
        int userId;
        String name;
        // other fields
        }
        public class UserNameAndAllPets {
        public int id;
        public String name;
        @Relation(parentColumn = "id", entityColumn = "userId")
        public List<Pet> pets;
        }

        @Dao
        public interface UserPetDao {
        @Query("SELECT id, name from User")
        public List<UserNameAndAllPets> loadUserAndPets();
        }
      • anvata:翻译的不够灵活,不过还是感谢楼主
        CyrusChan:我也觉得是这样,感觉看懂了,组织起来语句就变了味。。。
      • 你好_ddb0:插入的时候,我想根据某一个字段判断,如果是相同的字段就不再插入,这个可以实现吗?
        你好_ddb0:@CyrusChan 我现在用的是第二种方式,利用了唯一性约束。不过还有好多需要去学习的,谢谢博主的回复。
        CyrusChan:可以,第一种方式是先查,如果查到了就不插入了。 第二种利用唯一性约束,如果这个字段是主键的话,自动应该会禁止插入,如果是其他字段的话,可以在字段的注解上加上唯一性约束。
      • e1ddbd1bfac4:能不能把room封装到其他module
        CyrusChan:我不太明白你的意思,能不能说的具体点?
      • 洛埋名:感谢翻译!
        请教一个问题,ROOM能不能自定义数据库的创建路径呢
        洛埋名:@CyrusChan 非常感谢,那就是说如果用ROOM的话,就没法定制数据库路径了哈。
        CyrusChan:@CyrusChan sof问题路径 https://stackoverflow.com/questions/48903918/how-to-change-the-default-database-file-location-of-room-database?noredirect=1&lq=1
        CyrusChan:我在sof上查了下你的问题,没有找到解决方案。如果你想自定义数据库的创建路径,sof上给的建议是用SQLiteOpenHelper。
        ···
        public class MySQLiteOpenHelper extends SQLiteOpenHelper {
        MySQLiteOpenHelper(Context context) {
        super(context, "/mnt/sdcard/database_name.db", null, 0);
        }
        }
        ···
      • Il_mondo:模糊查询的语句怎么实现呢?
        例:SELECT * FROM table_name WHERE name LIKE '%王%'
        Il_mondo:@CyrusChan @Query("SELECT * FROM Accounts WHERE username LIKE '%:username%'")
        List<Account> test(String username);

        -------
        Error:(43, 19) 错误: Unused parameter: username
        会报这样的错误,不知道你遇到没有
        CyrusChan:@Query(SELECT * FROM table_name WHERE name LIKE '%王%') 其他的照着上面的例子来写就行了。
      • Cosecant:楼主你好,@Query可以执行删除?
        CyrusChan:@Cosecant 可以的,@Delete(此处填入你的删除语句,参数的传入可以模仿上面的查询语句)
        Cosecant:@CyrusChan @Delete可以执行复杂的删除吗
        CyrusChan:我觉得不可以吧,为什么不用@Delete注解呢?
      • 小于小于:楼主,update方法没有根据参数设置的功能吗?
      • android0226:不错,英语不过关的我非常有收益
      • 五谷观精分道长:List 数据如何存储

        Error:(24, 31) 错误: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
        小刘哥可爱多:@五谷观精分道长 不是外键关联表吗
        五谷观精分道长:@小刘哥可爱多 已了解 和 greendao一样 需要自己写转换器
        小刘哥可爱多:同问啊,list类型怎么办
      • 4fc0238b5269:有demo吗?
      • 飞翔的泥巴:写的非常好!
        CyrusChan:过奖了,我只是翻译的:grin: 哈哈

      本文标题:Android Room Orm框架学习

      本文链接:https://www.haomeiwen.com/subject/uppscxtx.html