JetPack--Room数据库

2021-12-06 17:28:33 浏览数 (1)

JetPack提供了Room数据库,和GreenDAO等开源库一样,在SQLite做了封装
Room主要使用三个注解:
1.Entity:实体类,对应一张表
2.Dao:包含操作表的一些列方法
3.Database:数据库持有者,数据库驱动。需要满足:定义的类是一个继承RoomDatabase的抽象类,注解中定义包含实体类列表,包含一个没有参数的抽象方法并返回Dao对象
一、Room上手

首先添加依赖:

代码语言:javascript复制
    implementation 'androidx.room:room-runtime:2.3.0-rc01'
    annotationProcessor 'androidx.room:room-compiler:2.3.0-rc01'

定义一个实体类,在class上使用 @Entity注解 ,还需要一个构造方法,Room会根据这个构造将表里的数据转化为实体类,对于其他我们代码里使用的构造方法,可以使用@Ignore注解表示Room将忽略它,属性也可以使用这个注解,表示这个属性将不会生成数据库字段 使用@PrimaryKey注解指定主键并且是自增长的 属性还可以指定在数据库的字段等,使用@ColumnInfo注解

代码语言:javascript复制
package com.aruba.room;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;

/**
 * Created by aruba on 2021/9/12.
 */
@Entity
public class User {
    @PrimaryKey(autoGenerate = true)
    public int id;
    public String name;
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    public int age;
    @Ignore
    public boolean flag;

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

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

    @Ignore
    public User() {
        
    }
}

定义Dao接口来对刚刚的User表进行操作,对接口使用@Dao注解 @Query、@Insert、@Delete、@Update注解,分别表示:查询、新增、删除、更新 增删改操作内部会自动使用主键进行操作

代码语言:javascript复制
package com.aruba.room;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
@Dao
public interface UserDao {
    //查询
    @Query("SELECT * FROM user")
    List<User> getUsers();

    //根据id查询
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一条数据
    @Insert
    void insertUser(User user);

    //删除一条数据
    @Delete
    void deleteUser(User user);

    //更新一条数据
    @Update
    void updateUser(User user);
}

定义抽象类,继承于RoomDatabase,并使用@Database注解,注解中指定表的实体类、数据库版本、是否输出日志 使用单例模式时,构造方法不能私有化,因为Room内部会调用构造方法 定义获取Dao对象的抽象函数

代码语言:javascript复制
package com.aruba.room;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

/**
 * Created by aruba on 2021/9/12.
 */
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {
    private static final String DBNAME = MyDataBase.class.getSimpleName();
    private static MyDataBase instance;

    public static MyDataBase getInstance() {
        if (instance == null) throw new NullPointerException("database not init!!");
        return instance;
    }

    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .build();

        return instance;
    }

    //获取Dao对象
    public abstract UserDao getUserDao();
}

界面中使用一个RecyclerView展示User表内的数据,并使用四个按钮分别进行查询,新增,删除,修改操作。 效果:

不过每次我们做了操作后,还需要手动查询下,有没有可以自动刷新数据的方法呢?

二、ViewModel LiveData Room

Room支持返回LiveData类型,结合ViewModel、DataBinding,就可以改造成一个非常棒的MVVM架构

代码语言:javascript复制
package com.aruba.room;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
@Dao
public interface UserDao {
    //查询
    @Query("SELECT * FROM user")
    LiveData<List<User>> getUsers();

    //根据id查询
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一条数据
    @Insert
    void insertUser(User user);

    //删除一条数据
    @Delete
    void deleteUser(User user);

    //删除所有数据
    @Query("DELETE  FROM user")
    void deleteAllUser();

    //更新一条数据
    @Update
    void updateUser(User user);
}

首先定义Repository层,里面实现对数据库的操作

代码语言:javascript复制
package com.aruba.room;

import android.content.Context;
import android.os.AsyncTask;
import android.view.View;

import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
public class UserRepository {
    private UserDao userDao;

    public UserRepository(Context context) {
        this.userDao = MyDataBase.init(context).getUserDao();
    }

    public void insert(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.insertUser(user);
                return null;
            }
        }.execute();
    }

    public void update(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.updateUser(user);
                return null;
            }
        }.execute();
    }

    public void delete(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.deleteUser(user);
                return null;
            }
        }.execute();
    }

    public void deleteAllUser(){
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.deleteAllUser();
                return null;
            }
        }.execute();
    }
    
    public LiveData<List<User>> query() {
        return userDao.getUsers();
    }
}

定义ViewModel

代码语言:javascript复制
package com.aruba.room;

import android.app.Application;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
public class UserViewModel extends AndroidViewModel {
    private UserRepository userRepository;

    public UserViewModel(@NonNull Application application) {
        super(application);
        userRepository = new UserRepository(application);
    }

    public void insert(View v) {
        userRepository.insert(new User("张三", 12));
    }

    public void update(View v) {
        User user = new User("赵四", 18);
        user.id = 1;
        userRepository.update(user);
    }

    public void delete(View v) {
        User user = new User();
        user.id = 2;
        userRepository.delete(user);
    }

    public void deleteAll(View v) {
        userRepository.deleteAllUser();
    }
    
    public LiveData<List<User>> getUsers() {
        return userRepository.query();
    }
}

Activity中使用:

代码语言:javascript复制
package com.aruba.room;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import com.aruba.room.databinding.ActivityMainBinding;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private RecyclerViewAdapter recyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserViewModel userViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(UserViewModel.class);
        activityMainBinding.setUserViewModel(userViewModel);
        activityMainBinding.setLifecycleOwner(this);

        userViewModel.getUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                recyclerViewAdapter.setDatas(users);
                recyclerViewAdapter.notifyDataSetChanged();
            }
        });
        recyclerViewAdapter = new RecyclerViewAdapter();
        activityMainBinding.recyclerview.setAdapter(recyclerViewAdapter);
        activityMainBinding.recyclerview.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
    }
}

效果:

三、升级与填充
1.使用Migration升级数据库

定义Migration,构造时需要低版本号和高版本号,初始化数据库时,通过addMigrations方法传入

代码语言:javascript复制
package com.aruba.room;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

/**
 * Created by aruba on 2021/9/12.
 */
@Database(entities = {User.class}, version = 3, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {
    private static final String DBNAME = MyDataBase.class.getSimpleName();
    private static MyDataBase instance;

    public static MyDataBase getInstance() {
        if (instance == null) throw new NullPointerException("database not init!!");
        return instance;
    }

    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                    .build();

        return instance;
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE user ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
        }
    };

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE user ADD COLUMN height INTEGER NOT NULL DEFAULT 1");
        }
    };

    //获取Dao对象
    public abstract UserDao getUserDao();
}
2.异常处理

如果我们将版本升级到3,但是没有写相应的Migration,那么会出现一个IIlegalStateException异常,使用fallbackToDestructiveMigration方法,出现异常时,会重新构造表,当然以前的数据会丢失

3.Schema文件

我们在使用@Database注解exportSchema指定为true,那么每次升级时,都会导出一个Schema文件,里面包含的数据库的创建信息,方便排查问题 同时我们也需要在gradle里指定下导出文件夹位置

代码语言:javascript复制
    defaultConfig {
        applicationId "com.aruba.room"
        minSdk 21
        targetSdk 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        dataBinding {
            enabled = true
        }
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }
代码语言:javascript复制
{
  "formatVersion": 1,
  "database": {
    "version": 3,
    "identityHash": "5a971aace7f8ede39ea6eb469ab90b10",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `age` INTEGER NOT NULL, `sex` INTEGER NOT NULL, `height` INTEGER NOT NULL)",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "name",
            "columnName": "name",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "age",
            "columnName": "age",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "sex",
            "columnName": "sex",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "height",
            "columnName": "height",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": true
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "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, '5a971aace7f8ede39ea6eb469ab90b10')"
    ]
  }
}
4.销毁与重建策略

SQLite中修改表结构比较麻烦,如果想要将sex字段从INTEGER改为TEXT,最好的方式是采用销毁与重建策略,将数据复制到一个临时表,在删除原表,再将临时表重命名成原表名,可以参考schema文件

代码语言:javascript复制
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` 
            // (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT
            // , `age` INTEGER NOT NULL, `sex` INTEGER NOT NULL, `height` INTEGER NOT NULL)",
            //根据schema文件创建新表,注意TEXT不需要加上NOT NULL
            database.execSQL(
                    "CREATE TABLE temp_user ("  
                            "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "  
                            "name TEXT, "  
                            "age INTEGER NOT NULL, "  
                            "sex TEXT DEFAULT 'M', "  
                            "height INTEGER NOT NULL )"
            );
            //数据导入临时表
            database.execSQL("INSERT INTO temp_user (name,age,sex,height)"  
                    "SELECT name,age,sex,height FROM user");
            //丢弃原表
            database.execSQL("DROP TABLE user");
            //临时表重命名
            database.execSQL("ALTER TABLE temp_user RENAME TO user");
        }
    };
5.预填充数据库

我们可以将数据库文件放入assets目录下,初始化数据库时,通过createFromAsset方法或createFromFile方法导入

代码语言:javascript复制
    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
                    .fallbackToDestructiveMigration()
                    .createFromAsset("mem.db")
                    .build();

        return instance;
    }
Demo地址:https://gitee.com/aruba/my-jetpack-application.git

0 人点赞