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