安卓开发学习之Room数据库的使用

    闲来无事,总结一下前一阵子对Room数据开源库的学习。


    数据库存储是安卓开发中数据存储的方式之一,并且安卓为我们提供了sqlite数据库和SQLiteOpenHelper等等强大的开发支持。而Room,则是对它们的一层封装,为的是让我们更加方便地操作sqlite数据库。

    

    以下,为使用步骤:

加入依赖

implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

创建实体类

 创建队员类(User)

@Entity(tableName = "users", //表名
        primaryKeys = {"id", "name"},//主键
        indices = {//索引
                @Index(value = "id", unique = true) //唯一性
        }) //实体
public class User implements Comparable {
    @android.support.annotation.NonNull
    @ColumnInfo(name = "id")
    private long id;

    @android.support.annotation.NonNull
    @ColumnInfo(name = "name")
    private String name;

    @ColumnInfo(name = "position")
    private String position;

    @Embedded
    private UserPerforms performs;

    @Ignore
    public User() {
    }

    public User(long id, String name, String position) {
        this.id = id;
        this.name = name;
        this.position = position;
    }

    // ...get/set+toString()

    @Override
    public int compareTo(@NonNull Object o) {
        if (o instanceof User) {
            User u = (User) o;
            if (this.id > u.id) {
                return 1;
            } else if (this.id < u.id) {
                return -1;
            } else if (this.id == u.id) {
                return 0;
            }
        }
        return 0;
    }
}

                    这个类里面的东西我解释一下:

                    @Entity,表示这个类是数据库中的一张表(实体)

                    @Embedded,表示在这个表中嵌入另一个类(其实就是嵌了一张表)

                    @NonNull,主键必须声明非空,而且要在@ColumnInfo上面

                    @ColumnInfo(name = "id") 表示这个属性是数据库表的某一列,列名是id

                    @Ignore,让room忽略(我是标在无参构造方法上的,表示不让room用这个构造方法构造对象)

                    compareTo()方法,这个我是为了保证有序,覆写了一下


 创建队员表现类

@Entity(tableName = "performs",
        primaryKeys = "p_id", // 主键
        foreignKeys = @ForeignKey(entity = User.class
                , parentColumns = "id"
                , childColumns = "p_id"
                , onUpdate = ForeignKey.CASCADE
                , onDelete = ForeignKey.CASCADE)) 
public class UserPerforms {
    @android.support.annotation.NonNull 
    @ColumnInfo(name = "p_id")
    private long p_id;

    @ColumnInfo(name = "score")
    private int score;

    @ColumnInfo(name = "assist")
    private int assist;

    @Ignore
    public UserPerforms() {
    }

    public UserPerforms(long p_id, int score, int assist) {
        this.p_id = p_id;
        this.score = score;
        this.assist = assist;
    }

    //...get/set+toString()
}

                注意里面外键的定义:

foreignKeys = @ForeignKey(entity = User.class
        , parentColumns = "id"
        , childColumns = "p_id"
        , onUpdate = ForeignKey.CASCADE
        , onDelete = ForeignKey.CASCADE)

                entity:关联表名(类)

                parentColumns:父列名

                childColumns:子列名

                onUpdate,onDelete:父列列值发生变化时,子列的处理方式(我都用的级联,省事儿)


创建数据库类

    此时要继承RoomDatabase这个类。这里,我用了下单例

@Database(entities = {User.class, UserPerforms.class}, version = 1, exportSchema = true)
public abstract class DatabaseCreator extends RoomDatabase {
    private static DatabaseCreator databaseCreator;

    public static DatabaseCreator getInstance(Context context) {
        if (databaseCreator == null) {
            synchronized (DatabaseCreator.class) {
                if (databaseCreator == null) {
                    databaseCreator = Room.databaseBuilder(context.getApplicationContext(), DatabaseCreator.class,
                            "user_perfom_2018.db").addMigrations(new Migration(1, 2) {
                        //版本升级.从1升到2
                        @Override
                        public void migrate(@NonNull SupportSQLiteDatabase database) {
                            database.execSQL("create table coach(id int,name varchar(30),age int,constraint pk_coach PRIMARY_KEY(id) )");
                        }
                        //版本升级并不方便,也不提倡频繁对数据库进行组织结构的变动
                    }).build();
                }
            }
        }
        return databaseCreator;
    }

    public static void onDestroy() {
        databaseCreator = null;
    }

    public abstract CRUDDAO getDao();
}
                类的说明:

                @Database,说明这是一个room的数据库类。里面的参数:entities说明数据库里有哪些表(实体),version是版本号,exportSchema是是否允许导出,默认为true(导出后的数据库什么样的,我们最后看看)

                单例里面Room.databaseBuilder就是构造一个数据库构造者,参数列表是上下文,数据库类名和数据库文件名

                addMigrations()是升级数据库时用的,我们在构造完数据库后,每对数据库表的一次结构变化操作,都要调用这个方法升级数据库,并在migrate()方法中执行升级的语句。所以,升级不方便,也别没事儿就改变数据库表的结构。

                抽象方法getDao(),这个是获取数据库操作接口的方法,不用我们实现,编译通过后,room会帮我们实现


DAO接口

    方才说了获取数据库操作接口,这个接口要我们自己声明,但不用实现。

    这个接口,无非就是CRUD增查改删,不同的操作要用上不同的注解。我一个一个说:


声明

    声明接口,配上注解@Dao:

@Dao
public interface CRUDDAO {}


查询所有

   查询所有User,配上@Query注解,并给注解传入sql语句

@Query("select * from users")
List getAllUsers();
//查询方法返回值必须是cursor或arrayList


多表+筛选查询

    可以从方法参数列表中给sql传入参数,从参数列表传入的要加上:(冒号)以示区分

@Query("select users.id,users.name from users,performs " +
        "where users.id = performs.p_id and performs.score > :score and performs.assist > :assist")
    // 多表+筛选查询
List getUserWithLimits(int score, int assist);

            可以看到,我的返回值List里的元素是UserSimple类型的,因为我们的sql是只查询了队员的号码和名字,而我们又在User类中固定了room能用的构造方法参数为3个(号码,名字,位置),这就出现了不匹配,room会编译时就拉闸的。

            所以我又针对号码和名字建立了一个新的类UserSimple,由于这不是数据库中的表,所以不用也不能使用@Entity(用了就要addMigrate()),只需要注名属性和列名的对应关系就中。代码如下:

public class UserSimple {
    @ColumnInfo(name = "id")
    private String id;

    @ColumnInfo(name = "name")
    private String name;

    //...get/set和toString()
}


插入

    配上@Insert注解和冲突政策(为了省事儿,我直接替换)

@Insert(onConflict = OnConflictStrategy.REPLACE)
    //插入有冲突,就直接替换
void insert(User[] users);


删除

由于我们定义外键时写了级联,所以这儿只配上一个@Delete就中

@Delete
int delete(User user);
//删除方法返回值必须是int或void


更新

配上@Update注解

@Update
int update(User user);
//更新方法返回值也必须是int或void,但不要这么更新,要么用query,指定sql语句,要么直接重新插入
//实际覆盖插入也可以实现更新

          

查询更新(推荐)

    正如上面的注释所说,@Update并不能生成正确的sql语句(不信你单步调试看看),所以更新最好利用@Query注解,而后传入更新的sql就中了

@Query("update users set name=:name where id = :id")
void updateName(String name, long id);


使用

    最好在Fragment中使用。另外,room默认是让数据库的IO操作必须放在子线程里,否则程序直接宕掉。

    我为了方便,用了ButterKnife的注入和线程池的构造新线程的方法,但这里我们只看跟数据库有关的内容。

设置初始数据

private ArrayList usersAll = new ArrayList();
private ArrayList performsAll = new ArrayList()
private void setInfo() {
    usersAll.add(new User(11, "Jason", "中锋"));
    usersAll.add(new User(17, "Mike", "前腰"));
    usersAll.add(new User(2, "Kane", "中卫"));
    usersAll.add(new User(8, "Handerson", "后腰"));
    usersAll.add(new User(1, "Frank", "门将"));
    performsAll.add(new UserPerforms(11, 32, 15));
    performsAll.add(new UserPerforms(1, 0, 0));
    performsAll.add(new UserPerforms(17, 20, 23));
    performsAll.add(new UserPerforms(8, 6, 10));
    performsAll.add(new UserPerforms(2, 3, 4));
}


提取DAO

    为了使用方便,我把获取DAO接口的方法提取一下:

public class CRUDUtil {
    public static CRUDDAO getDao(Context context) {
        return DatabaseCreator.getInstance(context).getDao();
    }
}

    可以看到,这儿就是调用了我们自己的DatabaseCreator类的抽象方法--getDao()     

                

构造与DB交互类

    构造一个模型和数据库交互的类,名曰MyModel,里面提供增查改删的方法,以供Fragment调用(为了方便,我就不用泛型了,直接重载)

public void insert(User[] data, Context context) {
    CRUDUtil.getDao(context).insert(data);
}

public void insert(UserPerforms[] data, Context context) {
    CRUDUtil.getDao(context).insert(data);
}

public void update(User data, Context context) {
    CRUDUtil.getDao(context).updateName(data.getName(), data.getId());
}

public ArrayList getAllUsers(Context context) {
    return (ArrayList) CRUDUtil.getDao(context).getAllUsers();
}

public ArrayList getAllPerforms(Context context) {
    return (ArrayList) CRUDUtil.getDao(context).getAllPerforms();
}

public ArrayList getUsersWithLimits(Context context, int score, int assist) {
    return (ArrayList) CRUDUtil.getDao(context).getUserWithLimits(score, assist);
}

public void delete(User u, Context context) {
    CRUDUtil.getDao(context).delete(u);
}


测试

ThreadUtil.executeRunnable(new Runnable() {
    @Override
    public void run() { // 插入数据
        nameModel.insert(usersAll.toArray(new User[1]), getContext());
        nameModel.insert(performsAll.toArray(new UserPerforms[1]), getContext());
    }
});
ThreadUtil.executeRunnable(new Runnable() {
    @Override
    public void run() { // 更新数据
        usersAll.get(1).setName("szc");
        nameModel.update(usersAll.get(1), getContext());
    }
});
ThreadUtil.executeRunnable(new Runnable() {
    @Override
    public void run() { // 删除数据
        for (int i = 0; i < usersAll.size(); i++) {
            nameModel.delete(usersAll.get(i), getContext());
            sleep(1000);
        }
    }
});
ThreadUtil.executeRunnable(new Runnable() {
    @Override
    public void run() {
        usersShow = (ArrayList) CRUDUtil.getDao(getContext()).getAllUsers();
        
        // handler + message 遍历
    }
});
ThreadUtil.executeRunnable(new Runnable() {
    @Override
    public void run() {
        ArrayList userSimples = (ArrayList) CRUDUtil.getDao(getContext()).getUserWithLimits(10,10);

        // handler + message 遍历
    }
});

            ThreadUtil是我自己封装的一个类,就是利用线程池执行子线程


利用RxJava构造响应式数据库

    可以看到,虽然上面已经可以方便地对数据库进行CRUD操作,但是数据库的数据每变化一次,我们就要主动调用方法获取一次,稍显麻烦。为此,我们可以用CompositeDisposable对象充当观察者,进行监听。


获取Flowable对象      

    在CRUD接口中增加查询方法,返回值中List的元素变成Flowable对象

@Dao
public interface CRUDDAO {
    ....
    @Query("select * from users")
    Flowable> getAllUsersFlowable();
    ....
}


更新交互类

    在MyModel中加入相应方法

public Flowable> getAllUsersFlowable(Context context) {
    return CRUDUtil.getDao(context).getAllUsersFlowable();
}


创建并初始化CompositeDisposable

    在Fragment中合适部分(我选的是onCreate()中),创建并初始化CompositeDisposable对象

private CompositeDisposable disposable = new CompositeDisposable();
disposable.add(nameModel.getAllUsersFlowable(getContext())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer>() { // Flowable变成Disposable
            @Override
            public void accept(List users) throws Exception {
                Collections.sort(users);
                traverseList((ArrayList) users); // 遍历
            }
        }));

    这样,数据库User表一有动静(因为我们是获取的User表的Flowable),disposable对象就会观察到,调用getAllUsersFlowable()方法,不出意外的话,accept方法就会被调用,遍历就会被进行。    


查看导出内容

    最后,我们看一下数据库导出的内容(工程目录下schemas/包/json文件):

{
  "formatVersion": 1,
  "database": {
    "version": 1,
    "identityHash": "54e23e816a8145dfacd8880f58915619",
    "entities": [
      {
        "tableName": "users",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `position` TEXT, `p_id` INTEGER, `score` INTEGER, `assist` INTEGER, PRIMARY KEY(`id`, `name`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "name",
            "columnName": "name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "position",
            "columnName": "position",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "performs.p_id",
            "columnName": "p_id",
            "affinity": "INTEGER",
            "notNull": false
          },
          {
            "fieldPath": "performs.score",
            "columnName": "score",
            "affinity": "INTEGER",
            "notNull": false
          },
          {
            "fieldPath": "performs.assist",
            "columnName": "assist",
            "affinity": "INTEGER",
            "notNull": false
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id",
            "name"
          ],
          "autoGenerate": false
        },
        "indices": [
          {
            "name": "index_users_id",
            "unique": true,
            "columnNames": [
              "id"
            ],
            "createSql": "CREATE UNIQUE INDEX `index_users_id` ON `${TABLE_NAME}` (`id`)"
          }
        ],
        "foreignKeys": []
      },
      {
        "tableName": "performs",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`p_id` INTEGER NOT NULL, `score` INTEGER NOT NULL, `assist` INTEGER NOT NULL, PRIMARY KEY(`p_id`), FOREIGN KEY(`p_id`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
        "fields": [
          {
            "fieldPath": "p_id",
            "columnName": "p_id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "score",
            "columnName": "score",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "assist",
            "columnName": "assist",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "p_id"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": [
          {
            "table": "users",
            "onDelete": "CASCADE",
            "onUpdate": "CASCADE",
            "columns": [
              "p_id"
            ],
            "referencedColumns": [
              "id"
            ]
          }
        ]
      }
    ],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"54e23e816a8145dfacd8880f58915619\")"
    ]
  }
} 

你可能感兴趣的:(安卓开发,安卓开源库)