androd Room和LiveData

概述

Room在SQLite上提供了一个抽象层, 能让开发者更简单便利的访问SQLite。

Database

  • 用法:此组件用于创建数据库的持有者,同时在类层级上使用注解来定义一系列的Entity,这些Entity对应着数据库中的表格。Database类中必须包含DAO对象获取。Database是App层与底层SQLite之间的连接点。在应用中要使用此组件的话需要继承RoomDatabase。然后通过Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder().获得该类的实例。因为创建数据库开销很大,所以建议设置为单例
@Database(entities = {User.class}, version = 2)
public abstract class UserDatabase extends RoomDatabase{
    private static UserDatabase sDatabase;
    private static Object sObject = new Object();

    public static UserDatabase getInstance(Context context) {
        if(sDatabase == null) {
            synchronized (sObject) {
                if(sDatabase == null){
                    //允许主线程访问数据库,默认是不允许
//                    sDatabase = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, "users.db")
//                            .allowMainThreadQueries()
//                            .build();
                    sDatabase = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, "users.db")
                            .build();
                }
            }
        }
        return sDatabase;
    }

    public abstract UserDao getUserDao();//必须包含Dao
}
  • 数据库升级

Room提供了 Migration 类用于迁移数据库,每一个 Migration 需要在构造函数里指定开始版本和结束版本。在运行时,Room会按照提供版本的顺序顺序执行每个Migration的migrate()方法,将数据库升级到最新的版本。

@Database(entities = {User.class}, version = 2)
public abstract class UserDatabase extends RoomDatabase{
    private static UserDatabase sDatabase;
    private static Object sObject = new Object();
    private static Migration migrations1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE users_tb " + " ADD COLUMN address1_city TEXT default null");
            database.execSQL("ALTER TABLE users_tb " + " ADD COLUMN address2_city TEXT default null");
            Log.i("room---", "upgrade 1 to 2");
        }
    };

    public static UserDatabase getInstance(Context context) {
        if(sDatabase == null) {
            synchronized (sObject) {
                if(sDatabase == null){
                    //允许主线程访问数据库,默认是不允许
//                    sDatabase = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, "users.db")
//                            .allowMainThreadQueries()
//                            .build();
                    sDatabase = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, "users.db")
                            .addMigrations(migrations1_2)
                            .build();
                }
            }
        }
        return sDatabase;
    }

    public abstract UserDao getUserDao();
}

Entity

  • 此组件的一个实例表示数据库的一行数据,对于每个Entity类来说,都会有对应的table被创建。想要这些Entity被创建,就需要写在上面Database的注解参数entities列表中。默认Entity中的所有字段都会拿来创建表,除非在该字段上加上@Ignore注解。

  • 每个Entity至少定义一个主键,即使你的Entity只有一个字段也是如此。定义主键使用@PrimaryKey。如果你想让Room给你的Entity自动生成ID的话,可以使用@Primary的autoGenerate属性。如果Entity具有复合主键的话,可以使用@Entity的primaryKeys属性,参照下方代码:

@Entity(tableName = "users_tb")
public class User extends Object {
    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "user_name")
    public String name;

    @ColumnInfo(name = "user_age")
    public int age;

    @Override
    public String toString() {
        return "id=" + id + " name=" + name + " age=" + age + " address1=" + mAddress1 + " address2=" + mAddress2;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
  • 在适当的字段上添加索引可以加快数据库的访问速度,要在Entity上添加索引可以使用@Entity的indices属性,可以添加索引或组合索引
@Entity(tableName = "users_tb", indices = {@Index("user_age"), @Index("user_name")})
public class User extends Object {
    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "user_name")
    public String name;

    @ColumnInfo(name = "user_age")
    public int age;

    @Override
    public String toString() {
        return "id=" + id + " name=" + name + " age=" + age + " address1=" + mAddress1 + " address2=" + mAddress2;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
  • Room提供了一个注解@Embedded,允许在一个实体中嵌入另外一个实体,创建的表使用的是当前实体和嵌入实体的所有字段,所以我们可以修改上面的User实体,增加Adreess类如下,则会在数据库增加两个列:address1_city和address2_city,注意prefix修饰Embedded,因为此处有两个相同Address对象,所以要加前缀,如果只有一个Address对象,则可以不增加前缀,数据库会增加一个city的列。
public class Address {
    public String city;


    public Address(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "city:" + city;
    }
}

@Entity(tableName = "users_tb", indices = {@Index("user_age"), @Index("user_name")})
public class User extends Object {
    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "user_name")
    public String name;

    @ColumnInfo(name = "user_age")
    public int age;

    @Embedded(prefix = "address1_")
    public Address mAddress1;

    @Embedded(prefix = "address2_")
    public Address mAddress2;

    @Override
    public String toString() {
        return "id=" + id + " name=" + name + " age=" + age + " address1=" + mAddress1 + " address2=" + mAddress2;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setAddress1(Address address1) {
        mAddress1 = address1;
    }

    public void setAddress2(Address address2) {
        mAddress2 = address2;
    }

    public Address getAddress1() {
        return mAddress1;
    }

    public Address getAddress2() {
        return mAddress2;
    }
}
  • 外键
    SQLite是关系型数据库,你可以指定不同对象之间的关系。尽管大多数ORM类库允许对象之间互相引用,但Room明确禁止这一点。
    尽管不能使用直接关系,Room仍然在两个实体之间定义外键。
    例如,有另外一个实体Book,你可以使用@ForeignKey注解定义和User之间的关系。
@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;
}

DAO

  • 这个组件用来表示具有Data Access Object(DAO)功能的类或接口。DAO类是Room的重要组件,负责定义访问数据库的方法。继承RoomDatabase的类必须包含一个0参数且返回DAO类的方法。当在编译期生成代码的时候,Room会创建实现此DAO的类。负责增删改查的操作,通过对应的注解即可实现,对于参数使用如下queryByAge方法。
@Dao
public interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insert(User user);

    @Delete
    int delete(User user);

    @Query("select * from users_tb")
    List queryAll();

    @Update
    int update(User user);

    @Query("select * from users_tb where user_age= :age")
    int queryByAge(int age);
}
  • 返回列子集
    多数情况下,你只需要获取实体的少数几个字段。例如,你的ui可能只展示用户的姓名和年龄,而不是每个用户的详细信息。通过只获取需要的列,可以节省资源,并且查询速度更快。只要可以将查询的结果映射到返回的对象上,Room允许返回任何java对象。例如,可以创建如下java对象来获取用户的名和姓。
public class SimpleInfo {
    @ColumnInfo(name = "user_name")
    public String name;
    @ColumnInfo(name = "user_age")
    public int age;
}

@Dao
public interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insert(User user);

    @Delete
    int delete(User user);

    @Update
    int update(User user);

    @Query("select * from users_tb where user_age= :age")
    int queryByAge(int age);

    @Query("select * from users_tb")
    List queryAll();

    @Query("select user_name, user_age from users_tb")
    List querySimpleInfo();
}
  • 集合操作&返回值

room dao支持对数据集合/数组进行操作如下,如果insert方法只接受一个参数的话,表示仅仅插入一条数据,这是这个方法可以返回一个long型值,为新行的id。如果参数为数组或集合,则需要返回对应的long[]或者List,update 方法可以返回一个int型数据,表示此次修改影响到的行数, delete 方法可以返回一个int型数据,表示此次删除的行数。

@Dao
public interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insert(User user);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(List<User> userList);

    @Delete
    int delete(User user);

    @Delete
    void deleteList(List<User> userList);

    @Update
    int update(User user);

    @Update
    void updateList(List<User> userList);

    @Query("select * from users_tb where user_age= :age")
    int queryByAge(int age);

    @Query("select * from users_tb")
    List queryAll();

    @Query("select user_name, user_age from users_tb")
    List querySimpleInfo();
}
  • 类型转换

Room中的类型转换支持你将某个类的值存储到某一列中,为此Room提供了TypeConverter这个类用于将自定义类转换成Room所支持的类型。

例如我们想要将Date对象进行存储,我们可以这么写:

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();
    }
}

这样定义完以后,下次Room遇到Date,就能将其转换成Room所支持的Long了。

下面看看AppDatabase要怎么写:

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

在UserDatabase上添加TypeConverters注解,并将Converter作为其参数。

接着User实体:

Room、GreenDao和ORMLite比较

ORMLite

ORMLite是一款通过反射完成对象关系映射的数据库框架。其Android部分在github上有1.2k的star和0.3k的Fork。其使用简单,但由于使用反射,造成了一定的性能开销,其自身提供了ormlite_config机制通过读取文件内容绕过反射来创建数据表。其主要特性如下:

1.使用反射来完成对象关系的映射,速度较慢。

2.使用类sql描述sql语句,比如where.eq(“name”,name).and().eq(“deleteTime”,0);

3.在insert操作后自动设置数据的主键。

4.支持将父类的变量解析为数据库表字段。

5.支持sqlcipher。

6.提供connectionProxy,用于在CRUD等操作时进行统一的逻辑操作,如发送事件等。

GreenDao

GreenDao是Android平台的一款流行的对象关系映射数据库框架,在github上有8.4k的star和2.4k的fork,jar包大小140KB。
其主要特性如下:

1.使用自定义的gradle插件来完成sql相关代码的生成。该插件在GreenDao3.0版本后才开始支持,在3.0之前需要我们引入一个greendao generated项目用来生成代码。

2.它使用类sql来表示sql语句,类似ORMLite

3.他支持懒加载,在查找时,首先返回一个cursor,在我们需要使用到具体数据时,才将之前得到cursor转变为实体对象。

4.支持sqlcipher。

5.不支持将父类的变量解析为数据库表字段。

Room

Room同样为Android平台的一款对象关系映射框架,其为2017年谷歌IO推出的Android Architecture Component的一部分,其主要特性如下:

1.其使用谷歌官方的注解处理器annotationProcessor完成对注解的解析。

2.使用原生sql来表达对数据库的操作。会在编译时会验证字段名称是否匹配,如果有问题,则发生编译错误,而不是运行时故障。

3.它还支持同为Android Architecture Component的LiveData,实现数据的动态刷新和绑定组件生命周期功能。

4.他并不支持sqlcipher,需要我们使用第三方库来支持。

5.支持父类变量解析为数据库表字段。

6.默认会让主线程的数据库查询操作崩溃,可以通过allowMainThreadQueries绕过这个限制。

你可能感兴趣的:(android-实用技巧)