使用Room操作数据库
先看一下Room由哪几部分组成的
- Entity,用来封装实际数据的实体类,它和数据库中的表一一对应,有几张表就建立几个实体类,建立好Entity后,表中各列会自动生成。
- Dao, 将对数据库的各项操作都封装在这里,逻辑层直接和Dao打交道,不直接操作数据库。
- Database, 定义数据库的关键信息,如版本号,包含的实体类,以及提供Dao层的访问实例。
下面看一下使用方式
1、导入依赖
implementation 'androidx.room:room-runtime:2.1.0'
annotationProcessor "androidx.room:room-compiler:2.1.0"
2 构建实体类
这里构建一个简单的Book类,通过@Entity标注为实体类,它的所有字段默认都会称为表中的一列,默认类名作为表名,列名作为字段名。
package com.demo.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class Book {
@ColumnInfo() // 这里可以指定列的名称,但是我们不直接操作数据库,使用默认即可
private String name;
@PrimaryKey(autoGenerate = true)
private long bookId = 0;
@ColumnInfo
private int page;
@ColumnInfo
private float price;
public Book(String name, int page, float price) {
this.name = name;
this.page = page;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public long getBookId() {
return bookId;
}
public void setBookId(long bookId) {
this.bookId = bookId;
}
@NonNull
@Override
public String toString() {
return "name: "+getName()+" page: "+String.valueOf(getPage())+" price: "+String.valueOf(getPrice());
}
}
如果想要修改表名
@Entity(tableName = "book")
默认情况下,类中每个成员即便不添加ColumnInfo注解都会称为一列,如果不要它称为一列,可以通过@Ignore注解忽略
@Ignore
public float price;
主键,复合主键
上面我们使用@PrimaryKey指定单个主键,也可以通过@PrimaryKeys指定复合主键,比如有一个User类,我们希望将firstName和lastName作为联合主键,可以这样做。
@Entity(primaryKeys = {"firstName", "lastName"})
索引
如果希望加快访问速度,可以在@Entity中添加索引
@Entity(indeces = {@index("name")})
也可以添加多个索引
@Entity(indices = {@Index("name"), @Index(value = {"last_name", "address"})})
unique
有时候, 需要保证某个字段或者多个字段组成的组唯一,那么可以通过将@Index的unique属性设置为true来确保唯一性,比如我们希望firstName和lastName 的组合唯一。
@Entity( indices = {@index(value = {"firstName", "lastName"}, unique = true)})
外键
通过@ForeignKey定义外键
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo(name = "user_id")
public int userId;
}
上例中,我们通过将parentColumns和 childColumns 分别进行指定。
3、定义Dao
这里我定义的是一个接口,提供了对数据库的各项操作,如增删改查等功能,可以按照自己的需求定制
package com.demo.model;
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
public interface BookDao {
@Query("select * from Book")
List<Book> selectAll();
// 根据书名查找
@Query("select * from Book where name = (:bookName)")
List<Book> selectByName(String bookName);
@Update
void updatePrice(Book book);
@Query("delete from Book where name = :name") // 这里是删除操作,但是用的是Query注解,因为Delete注解不能加sql语句
void deleteBook(String name);
@Insert
void insertBook(Book book);
}
从上面的几个函数中可以知道,对于比较简单的操作,我们不需要提供sql语句,只需要进行注解即可,对于逻辑比较复杂的功能,我们提供sql语句,并且它会自动检查sql语句的正确性,重点关注一下sql语句传参的方式,通过 “:”获取参数。
插入时冲突处理
通过Insert插入数据时,可能会发生冲突,默认情况下处理方式是OnConflictStrategy.ABORT,会抛出异常,我们可以通过指定为OnConflictStrategy.REPLACE让新数据取代老数据。
@Insert(onConfict = OnConfictStrategy.REPLACE)
传入一个集合参数
@Dao
public interface MyDao {
@Query(“SELECT first_name, last_name FROM user WHERE region IN (:regions)”)
public List loadUsersFromRegions(List regions);
}
返回一个cursor对象
Room还支持返回一个cursor对象,但是觉得这样没有必要。
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
4、定义Database
package com.demo.model;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(version = 1,entities = {Book.class},exportSchema=true)
public abstract class BookDatabase extends RoomDatabase {
// 提供Dao的实例
public abstract BookDao bookDao();
// 单例
private BookDatabase database;
public BookDatabase getInstance(Context context){
if (database == null){
synchronized (BookDatabase.class){
if (database == null){
database = Room.databaseBuilder(context.getApplicationContext(),BookDatabase.class,"book.db").build();
}
}
}
return database;
}
}
定义一个 BookDatabase ,继承RoomDatabase,同时在注解中指明实体集entities,版本号version等, exportSchema 属性一般指定为true,否则可能会报错。对于获取BookDao实例的方法,我们也只需要声明方法即可,底层会自动完成。BookDatabase 结合单例模式进行设计,具体实例化使用方法Room.databaseBuilder()去完成。
下面看一个例子
package com.demo.dashboard;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.demo.model.AppDatabase;
import com.demo.model.Book;
import com.demo.model.BookDao;
import com.demo.model.BookDatabase;
import com.demo.model.EventMsg;
import com.demo.model.User;
import com.demo.model.UserDao;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
public class Activity2 extends AppCompatActivity {
private static final String TAG = "Activity2";
private BookDao bookDao;
private Button btnSearch;
private Button btnDelete;
private Button btnUpdate;
private Button btnInsert;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
EventBus.getDefault().register(this);
bookDao = BookDatabase.getInstance(this).bookDao();
Book book1 = new Book("安徒生童话",389,19.88f);
Book book2 = new Book("Android入门",469,88.88f);
EventBus.getDefault().post(new EventMsg(4,book1));
EventBus.getDefault().post(new EventMsg(4,book2));
btnSearch = findViewById(R.id.btn1);
btnDelete = findViewById(R.id.btn2);
btnUpdate = findViewById(R.id.btn3);
btnInsert = findViewById(R.id.btn4);
btnSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new EventMsg(1,null));
}
});
btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new EventMsg(2,new Book("Android入门",588,48.00f)));
}
});
btnUpdate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new EventMsg(3,new Book("Android入门",588,48.00f)));
}
});
btnInsert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 懒得搜集输入,这里随便写一个
EventBus.getDefault().post(new EventMsg(4,new Book("操作系统",400,38.00f)));
}
});
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void dealEventMsg(EventMsg msg){
switch (msg.type){
case 1:
Log.d(TAG, "dealEventMsg: ");
List<Book> bookList =bookDao.selectAll();
for (Book book:bookList){
Log.d(TAG, "bookInfo: "+book.toString());
}
break;
case 2:
bookDao.deleteBook(msg.book.getName());
break;
case 3:
bookDao.updatePrice(msg.book);
case 4:
bookDao.insertBook(msg.book);
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
对数据库的操作由于是耗时操作,所以必须放在子线程中完成,所以我在这里使用了EventBus机制。
5、数据库的升级
如果我们想要在Book中加入一个出版社,Room并没有提供比较简单的方法,和使用SQLHelper一样麻烦。
1、修改Book类
package com.demo.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class Book {
@ColumnInfo() // 这里可以指定列的名称,但是我们不直接操作数据库,使用默认即可
private String name;
@PrimaryKey(autoGenerate = true)
private long bookId = 0;
@ColumnInfo
private int page;
@ColumnInfo
private float price;
@ColumnInfo
private String chuban;
public Book(String name, int page, float price, String chuban) {
this.name = name;
this.page = page;
this.price = price;
this.chuban = chuban;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public long getBookId() {
return bookId;
}
public void setBookId(long bookId) {
this.bookId = bookId;
}
public String getChuban() {
return chuban;
}
public void setChuban(String chuban) {
this.chuban = chuban;
}
@NonNull
@Override
public String toString() {
return "name: "+getName()+" page: "+String.valueOf(getPage())+" price: "+String.valueOf(getPrice())
+" 出版社:"+getChuban();
}
}
2、修改BookDatabase
package com.demo.model;
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;
@Database(version = 2,entities = {Book.class})
public abstract class BookDatabase extends RoomDatabase {
// 提供Dao的实例
public abstract BookDao bookDao();
// 单例
private static BookDatabase database;
public static BookDatabase getInstance(Context context){
if (database == null){
synchronized (BookDatabase.class){
if (database == null){
// 提供一个Migration实例,指明迁移的版本号
Migration migration1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("alter table Book add column chuban text");
}
};
database = Room.databaseBuilder(context.getApplicationContext(),BookDatabase.class,"book.db")
.addMigrations(migration1_2)
.build();
}
}
}
return database;
}
}
上面有几个注意的点
- BookDatabase注解中指明新的版本号
- 提供一个迁移实例对象Migration,实现migrate方法
- 在创建Database的时候将migration添加进去
结果
没有做好重复的判断,有些数据重复插入了。