安卓系列之 Jetpack 架构(AAC):Room 基础篇
Google 官方推荐数据库,ORM(Object Relational Mapping)对象关系映射。
引用
dependencies {
def room_version = "2.3.0"
//基础配置
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
导出 json 文件的配置
json 文件---用于记录数据库升级的日志信息,便于观察数据库升级所更改的内容。
//指定数据库schema导出的位置
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
官方 room 框架图
基础知识
Entity
实体类,即对应表中的一行数据。
| 字段 | 意义 | 说明 |
|---|---|---|
| @Entity | 实体 | 在实体的 class 文件上注解 |
| tableName | 表名 | 数据表的名称 |
| @PrimaryKey | 主键 | 每个实体至少有一个主键 |
| (autoGenerate = true) | 自动分配 ID | 常用于 ID,自增长 |
| primaryKeys = {"userId"} | 复合主键 | |
| @ColumnInfo | 列名 | 自定义列名 |
| @Ignore | 忽略的字段或者构造方法 | 忽略,不生成在数据表里 |
| ignoredColumns | 忽略父类字段 | 实体继承父类,且不用该字段 |
Dao
封装访问数据库的方法,例如增删改查。
| 字段 | 意义 | 说明 |
|---|---|---|
| @Dao | DAO 类 | 封装访问方法 |
| @Insert | 增加 | 新增一条或者多条数据 |
| @Update | 更新 | 更新表中数据(需提供主键) |
| @Delete | 删除 | 删除表中数据(需提供主键) |
| @Query | 查询 | 查询表中数据 |
RoomDatabase
数据库持有者,内包含 Dao 的抽象对象方法,数据库升级方法,日志记录方法等。
实例
build.gradle 文件
- 在 android 的 defaultConfig 下新增数据库 json 日志文件输出地址配置:
//指定数据库schema导出的位置
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
- 在 android 下配置 Databinding:
dataBinding {
enabled = true
}
- 在 dependencies 下引入 room 和 baseAdapter 第三方:
//room
implementation 'androidx.room:room-runtime:2.2.6'
annotationProcessor 'androidx.room:room-compiler:2.2.6'
//baseAdapter (辅助列表展示的Adapter,不是数据库内容)
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
MusicBean 文件
建立实体 Bean 文件,内含表名,自增主键 Id,列名等数据。
package com.elaine.testroom.db.music;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
/**
* 音乐实体
* author: elaine
* date: 2021/7/9
* <p>
* Entity -----标记实体
* tableName = "elaine_music" -----表名
*/
@Entity(tableName = "elaine_music")
public class MusicBean {
/**
* 自增长ID,主键
* PrimaryKey ---主键
* autoGenerate = true ---自增长
*/
@PrimaryKey(autoGenerate = true)
private Long id;
/**
* 歌曲名称
*/
private String name;
/**
* mp3文件大小
*/
private int size;
/**
* 评论
*/
private String comment;
/**
* 歌手
*/
private String singer;
/**
* 是否收藏了
* ColumnInfo -----列信息
* name = "is_save" -----重命名列名
* typeAffinity = ColumnInfo.INTEGER -----存储类型
*/
@ColumnInfo(name = "is_save", typeAffinity = ColumnInfo.INTEGER)
private boolean isSave;
/**
* 是否在播放中
* Ignore -----忽略,即不写入数据库
*/
@Ignore
private boolean isPlay;
/**
* 构造方法
*/
public MusicBean(String name, int size, String comment, String singer, boolean isSave) {
this.name = name;
this.size = size;
this.comment = comment;
this.singer = singer;
this.isSave = isSave;
}
/**
* 构造方法
* Ignore -----忽略,即不写入数据库
*/
@Ignore
public MusicBean(String name, int size, String comment, String singer) {
this.name = name;
this.size = size;
this.comment = comment;
this.singer = singer;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getSinger() {
return singer;
}
public void setSinger(String singer) {
this.singer = singer;
}
public boolean isSave() {
return isSave;
}
public void setSave(boolean save) {
isSave = save;
}
public boolean isPlay() {
return isPlay;
}
public void setPlay(boolean play) {
isPlay = play;
}
}
MusicDao 文件
数据库相关操作,封装对 MusiceBean 对象的增删改查操作。
package com.elaine.testroom.db.music;
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;
/**
* 音乐表Dao
* author: elaine
* date: 2021/7/9
*/
@Dao
public interface MusicDao {
/**
* 插入数据
*
* @param musicBeans 实体
*/
@Insert
void insert(MusicBean... musicBeans);
/**
* 更新
*
* @param musicBeans 实体
*/
@Update
void update(MusicBean... musicBeans);
/**
* 删除
*
* @param musicBeans 实体
*/
@Delete
void delete(MusicBean... musicBeans);
/**
* 获取全部数据
*
* @return List<MusicBean>
*/
@Query("SELECT * FROM elaine_music")
LiveData<List<MusicBean>> getAll();
}
MusicRepository 文件
异步操作封装。
package com.elaine.testroom.db.music;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
import com.elaine.testroom.db.CommonDatabase;
import java.util.List;
/**
* 数据库异步处理
* author: elaine
* date: 2021/7/12
*/
public class MusicRepository {
private MusicDao musicDao;
public MusicRepository(Context context) {
musicDao = CommonDatabase.getInstance(context).getMusicDao();
}
/**
* 插入数据
* 异步执行
*
* @param musicBeans 实体
*/
@SuppressLint("StaticFieldLeak")
public void insert(MusicBean... musicBeans) {
new AsyncTask<MusicBean, Void, Void>() {
@Override
protected Void doInBackground(MusicBean... musicBeans) {
musicDao.insert(musicBeans);
return null;
}
}.execute(musicBeans);
}
/**
* 更新
* 异步执行
*
* @param musicBeans 实体
*/
@SuppressLint("StaticFieldLeak")
public void update(MusicBean... musicBeans) {
new AsyncTask<MusicBean, Void, Void>() {
@Override
protected Void doInBackground(MusicBean... musicBeans) {
musicDao.update(musicBeans);
return null;
}
}.execute(musicBeans);
}
/**
* 删除
* 异步执行
*
* @param musicBeans 实体
*/
@SuppressLint("StaticFieldLeak")
public void delete(MusicBean... musicBeans) {
new AsyncTask<MusicBean, Void, Void>() {
@Override
protected Void doInBackground(MusicBean... musicBeans) {
musicDao.delete(musicBeans);
return null;
}
}.execute(musicBeans);
}
/**
* 获取全部数据
* 结合LiveData,方便数据及时更新
*
* @return List<MusicBean>
*/
public LiveData<List<MusicBean>> getAll() {
return musicDao.getAll();
}
}
CommonDatabase 文件
数据库文件,内含数据库升级,dao 配置等操作。
package com.elaine.testroom.db;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.elaine.testroom.db.music.MusicBean;
import com.elaine.testroom.db.music.MusicDao;
/**
* author: elaine
* date: 2021/7/12
* <p>
* entities = MusicBean.class -----对象
* version = 1 -----当前数据库版本号
* exportSchema = true -----是否生成json数据库升级日志文件
*/
@Database(entities = MusicBean.class, version = 1, exportSchema = true)
public abstract class CommonDatabase extends RoomDatabase {
//数据库名字
private static final String DATABASE_NAME = "elaine_room";
//mInstance
public static CommonDatabase mInstance;
//单例
public static CommonDatabase getInstance(Context context) {
if (mInstance == null) {
synchronized (CommonDatabase.class) {
mInstance = Room.databaseBuilder(context.getApplicationContext(), CommonDatabase.class, DATABASE_NAME)
// .addMigrations(AppDataBase.MIGRATION_1_2) //若升级则加入这行代码
.build();
}
}
return mInstance;
}
//若需要升级则添加以下代码。数据库变动添加Migration,简白的而说就是版本1到版本2改了什么东西,
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `elaine_search_history` (`history` TEXT NOT NULL , `id` LONG PRIMARY KEY AUTOINCREMENT NOT NULL)");
}
};
/**
* 获取Dao
*
* @return MusicDao
*/
public abstract MusicDao getMusicDao();
}
item_music.xml 文件
recyclerview 的 item 文件,用于展示数据,内含删除和更新按钮。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="bean"
type="com.elaine.testroom.db.music.MusicBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{bean.name}"
android:textColor="@color/black"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@{bean.comment}"
android:textColor="#333333"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@{bean.singer}"
android:textColor="#666666"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@{String.valueOf(bean.size)+`MB`}"
android:textColor="#666666"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@{bean.save ? `已收藏`: `未收藏`}"
android:textColor="#666666"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除" />
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="更新" />
</LinearLayout>
</LinearLayout>
</layout>
activity_main.xml 文件
MainAcitity 的布局文件,内含 recyclerview 和新增按钮。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="insert"
android:text="新增" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_music"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</layout>
MusicAdapter 文件
列表适配器文件。
package com.elaine.testroom;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder;
import com.elaine.testroom.databinding.ItemMusicBinding;
import com.elaine.testroom.db.music.MusicBean;
import org.jetbrains.annotations.NotNull;
/**
* 列表适配器
* author: elaine
* date: 2021/7/9
*/
public class MusicAdapter extends BaseQuickAdapter<MusicBean, BaseDataBindingHolder<ItemMusicBinding>> {
public MusicAdapter() {
super(R.layout.item_music);
}
@Override
protected void convert(@NotNull BaseDataBindingHolder<ItemMusicBinding> baseDataBindingHolder, MusicBean bean) {
ItemMusicBinding mBinding = baseDataBindingHolder.getDataBinding();
if (mBinding != null) {
if (bean != null) {
mBinding.setBean(bean);
}
mBinding.executePendingBindings();
}
}
}
MainViewModel 文件
继承 AndroidViewModel,内含增删改查方法。
package com.elaine.testroom;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.elaine.testroom.db.music.MusicBean;
import com.elaine.testroom.db.music.MusicRepository;
import java.util.List;
/**
* author: elaine
* date: 2021/7/12
*/
public class MainViewModel extends AndroidViewModel {
private MusicRepository musicRepository;
public MainViewModel(@NonNull Application application) {
super(application);
musicRepository = new MusicRepository(application);
}
/**
* 插入数据
*
* @param musicBeans 实体
*/
public void insert(MusicBean... musicBeans) {
musicRepository.insert(musicBeans);
}
/**
* 更新
*
* @param musicBeans 实体
*/
public void update(MusicBean... musicBeans) {
musicRepository.update(musicBeans);
}
/**
* 删除
*
* @param musicBeans 实体
*/
public void delete(MusicBean... musicBeans) {
musicRepository.delete(musicBeans);
}
/**
* 获取全部数据
*
* @return List<MusicBean>
*/
public LiveData<List<MusicBean>> getAll() {
return musicRepository.getAll();
}
}
MainActivity 文件
package com.elaine.testroom;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.elaine.testroom.databinding.ActivityMainBinding;
import com.elaine.testroom.db.music.MusicBean;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mBinding;
private MainViewModel model;
private MusicAdapter musicAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
initViewModel();
initAdapter();
}
/**
* 初始化viewModel
*/
private void initViewModel() {
model = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MainViewModel.class);
//列表进行更改数据
model.getAll().observe(this, musicBeans -> {
if (musicBeans != null) {
musicAdapter.setList(musicBeans);
}
});
}
/**
* 初始化适配器
*/
private void initAdapter() {
musicAdapter = new MusicAdapter();
mBinding.rvMusic.setLayoutManager(new LinearLayoutManager(this));
mBinding.rvMusic.setAdapter(musicAdapter);
//子控件点击事件
musicAdapter.addChildClickViewIds(R.id.btn_delete, R.id.btn_update);
musicAdapter.setOnItemChildClickListener((adapter, view, position) -> {
MusicBean musicBean = musicAdapter.getData().get(position);
if (musicBean != null) {
if (view.getId() == R.id.btn_delete) {//删除操作
model.delete(musicBean);
} else if (view.getId() == R.id.btn_update) {//更新操作
musicBean.setSinger("歌手改" + (int) (Math.random() * 10));
model.update(musicBean);
}
}
});
}
/**
* 新增按钮点击事件
*/
public void insert(View view) {
MusicBean musicBean = new MusicBean("歌曲名称" + (int) (Math.random() * 10), (int) (Math.random() * 10), "评论", "歌手" + (int) (Math.random() * 10), !(Math.random() > 0.5));
model.insert(musicBean);
}
}
数据库效果图
界面效果图
数据库相关知识点
关键字
| 字段 | 说明 |
|---|---|
| DELETE | 删除 |
| FROM | 从哪张表 |
| WHERE | 条件判断 |
| and | 多个条件并列 |
| SELECT | 查询 |
| order by | 按照什么排序 |
| desc | 倒序 |
| limit | 限制数量 |
| set | 赋值 |
| UPDATE | 更新 |
| CREATE | 创建 |
| TABLE | 表 |
| NOT NULL | 不为空 |
| PRIMARY KEY | 主键 |
| AUTOINCREMENT | 自增长 |
| ALTER | 操作已有表 |
| ADD COLUMN | 增加列 |
| DEFAULT | 默认值 |
| INSERT INTO | 插入 |
| DROP TABLE | 删除表 |
| RENAME TO | 重命名 |
查询相关
- 查询指定表中的全部数据
SELECT * FROM 表名
- 根据 Id 查询指定表中的对象
SELECT * FROM 表名 WHERE id = :id
- 根据指定字段查询指定表中的对象
SELECT * FROM 表名 WHERE 字段= :变量
- 根据指定多个字段查询指定表中的对象
SELECT * FROM 表名 WHERE 字段= :变量 and 字段= :变量
- 根据指定字段查询指定数量的对象,并按照 id 倒序排序,eg.第一页
SELECT * FROM 表名 WHERE 字段= :变量 order by id desc limit 0,:pageSize(变量)
- 根据指定字段和 id<变量,查询指定数量的对象,并按照 id 倒序排序,eg.下一页
SELECT * FROM 表名 WHERE 字段= :变量 and id< :变量 order by id desc limit :pageSize (变量)
- 根据指定字段查询指定表中一共有多少条数据
SELECT count(*) FROM 表名 WHERE 字段= :变量
- 根据指定字段查询指定表中是否已存在,id>0 表示存在
SELECT id FROM 表名 WHERE 字段= :变量
- 根据制定字段查询指定表中某个字段是否是 ture,或者是否等于某个值
SELECT 字段(字段= :变量) FROM 表名 WHERE 字段= :变量
删除相关
- 删除指定表中全部数据
DELETE FROM 表名
- 根据 Id 删除指定表中的对象
DELETE FROM 表名 WHERE id= :id
- 根据指定字段删除指定表中的对象
DELETE FROM 表名 WHERE 字段= :变量
- 根据指定多个字段删除指定表中的对象
DELETE FROM 表名 WHERE 字段= :变量 and 字段= :变量
更新相关
- 根据指定字段更新指定表中的某个数据
UPDATE 表名 set 字段=:变量 WHERE 字段= :变量