JetPack提供了Room数据库,和GreenDAO等开源库一样,在SQLite做了封装
Room主要使用三个注解:
1.Entity:实体类,对应一张表
2.Dao:包含操作表的一些列方法
3.Database:数据库持有者,数据库驱动。需要满足:定义的类是一个继承RoomDatabase的抽象类,注解中定义包含实体类列表,包含一个没有参数的抽象方法并返回Dao对象
一、Room上手
首先添加依赖:
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注解:
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;@Ignorepublic boolean flag;public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}@Ignorepublic User(String name, int age) {this.name = name;this.age = age;}@Ignorepublic User() {}
}
定义Dao接口来对刚刚的User表进行操作,对接口使用@Dao注解@Query、@Insert、@Delete、@Update注解,分别表示:查询、新增、删除、更新
增删改操作内部会自动使用主键进行操作
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);//插入一条数据@Insertvoid insertUser(User user);//删除一条数据@Deletevoid deleteUser(User user);//更新一条数据@Updatevoid updateUser(User user);
}
定义抽象类,继承于RoomDatabase,并使用@Database注解,注解中指定表的实体类、数据库版本、是否输出日志
使用单例模式时,构造方法不能私有化,因为Room内部会调用构造方法
定义获取Dao对象的抽象函数
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架构
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);//插入一条数据@Insertvoid insertUser(User user);//删除一条数据@Deletevoid deleteUser(User user);//删除所有数据@Query("DELETE FROM user")void deleteAllUser();//更新一条数据@Updatevoid updateUser(User user);
}
首先定义Repository层,里面实现对数据库的操作
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>() {@Overrideprotected Void doInBackground(Void... voids) {userDao.insertUser(user);return null;}}.execute();}public void update(User user) {new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... voids) {userDao.updateUser(user);return null;}}.execute();}public void delete(User user) {new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... voids) {userDao.deleteUser(user);return null;}}.execute();}public void deleteAllUser(){new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... voids) {userDao.deleteAllUser();return null;}}.execute();}public LiveData<List<User>> query() {return userDao.getUsers();}
}
定义ViewModel
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中使用:
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;@Overrideprotected 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>>() {@Overridepublic 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方法传入
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) {@Overridepublic 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) {@Overridepublic 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里指定下导出文件夹位置
defaultConfig {applicationId "com.aruba.room"minSdk 21targetSdk 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"dataBinding {enabled = true}javaCompileOptions {annotationProcessorOptions {arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]}}}
{"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文件
static final Migration MIGRATION_3_4 = new Migration(3, 4) {@Overridepublic 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 NULLdatabase.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方法导入
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;}