Google官方Android的Room持久化库示例Demo
是时候该放弃 GreenDao的使用了,该使用Room持久化库喽~~~
理由:GreenDao库已经很少维护更新了,greenDao现在在较新的开发环境使用中会警告:
WARNING: API 'variant.getJavaCompiler()' is obsolete and has been replaced with 'variant.getJavaCompileProvider()'.
It will be removed at the end of 2019.
百度的解决方法:
解决方法:
将gradle 从高版本还原到 3.2以下
文档地址:https://developer.android.com/topic/libraries/architecture/room.html
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备之后重新连接到网络后,用户发起的所有内容更改都会同步到服务器。
由于 Room 负责为您处理这些问题,因此强烈建议您使用 Room(而不是 SQLite)。
注意:要在应用中使用 Room,请在应用的 build.gradle
文件中声明 Room 依赖项。
dependencies {
def room_version = "2.2.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
Room 包含 3 个主要组件:
-
数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用
@Database
注释的类应满足以下条件:- 是扩展
RoomDatabase
的抽象类。 - 在注释中添加与数据库关联的实体列表。
- 包含具有 0 个参数且返回使用
@Dao
注释的类的抽象方法。
在运行时,您可以通过调用
Room.databaseBuilder()
或Room.inMemoryDatabaseBuilder()
获取Database
的实例。 - 是扩展
-
Entity:表示数据库中的表。
-
DAO:包含用于访问数据库的方法。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
Room 不同组件之间的关系如图 所示:
创建实体和Dao:
package com.loaderman.roomdemo.bean;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
//Entity: 表示数据库内的表.
@Entity(tableName = "user", indices = {@Index(value = {"name"}, unique = true)})//有些时候, 数据库中的某些域或几组域必须是唯一的. 你可以通过将注解@Index的unique属性设置为true, 强制完成唯一的属性.
public class User {
@PrimaryKey(autoGenerate = true)//主键是否自动增长,默认为false
private int id;
@NonNull// 表示参数,成员变量或者方法返回值从不为null
@ColumnInfo(name = "name")//指定数据库表的列名。
private String name;
//演示版本2,升级数据库版本用, 演示版本1注释掉即可
private String phone;
private int age;
public User() {
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Ignore
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Ignore
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// getter setter方法必须写
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", age=" + age +
'}';
}
}
package com.loaderman.roomdemo.bean;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;
//@ForeignKey注解定义它和实体User的关系
/*
* 外键非常强大, 因为它允许你指定做什么操作, 在引用实体更新的时候. 比如, 你可以告诉SQLite为用户删除所有的书,
* 在相应的User实例被删除时, 而该User被Book通过在@ForeignKey注解里面声明onDelete = CASCADE而关联.
* 备注: SQLite将@Insert(onConflict = REPLACE)作为REMOVE和REPLACE的集合来操作, 而非单独的UPDATE操作. 这个取代冲突值的方法能够影响你的外键约束.
* */
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
public class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo(name = "user_id")
public int userId;
}
package com.loaderman.roomdemo.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.loaderman.roomdemo.bean.Book;
import com.loaderman.roomdemo.bean.User;
import java.util.List;
import io.reactivex.Flowable;
//DAO: 包含用于访问数据库的方法.
@Dao
public interface UserDao {
// OnConflictStrategy.REPLACE表示如果已经有数据,那么就覆盖掉
//数据的判断通过主键进行匹配,也就是uid,非整个user对象
//返回Long数据表示,插入条目的主键值(id)
@Query("SELECT * FROM user")
List<User> getAllUsers();
// LiveData是可以被观察到的数据持有类。它里面缓存或持有了最新的数据。当数据改变时会通知它的观察者。
// LiveData是可以感知生命周期的。UI组件只是观察相关数据,不会停止或恢复观察。
// LiveData自动管理所有这些,因为它在观察时意识到相关的生命周期状态变化。
@Query("SELECT * FROM user")
LiveData<List<User>> getAllUser();
@Query("SELECT * FROM user WHERE id=:id")
User getUser(int id);
@Query("SELECT * FROM user WHERE name=:name")
User getUser(String name);
@Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> insert(User... users);
@Insert(onConflict = OnConflictStrategy.REPLACE)
Long insert(User user);
@Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> insert(List<User> userLists);
@Update
int update(User... users);
@Update()
int updateAll(User... user);
@Update()
int updateAll(List<User> user);
@Delete
int delete(User user);
@Delete
int deleteAll(List<User> users);
@Delete
int deleteAll(User... users);
//返回RxJava2中的Publisher和Flowable.
@Query("SELECT * from user where name = :name LIMIT 1")
Flowable<User> getUserByName(String name);
//多表查询
@Query("SELECT * FROM book "
+ "INNER JOIN user ON user.id = book.user_id "
+ "WHERE user.id = :userName")
List<Book> findBooksByUserName(String userName);
}
AppDatabase.java
package com.loaderman.roomdemo.dao;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.loaderman.roomdemo.Converters;
import com.loaderman.roomdemo.bean.Book;
import com.loaderman.roomdemo.bean.User;
//注解指定了database的表映射实体数据以及版本等信息 推荐使用单例模式
@Database(entities = { User.class, Book.class }, version = 2,exportSchema = true)
//添加@TypeConverters注解到AppDatabase类上, 之后Room就能够在AppDatabase中定义的每一个实体和DAO上使用这个转换器.
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE user ADD COLUMN phone TEXT ");
}
};
public abstract UserDao getUserDao();
}
潜逃对象的创建示例:
package com.loaderman.roomdemo.bean;
import androidx.room.ColumnInfo;
import androidx.room.Embedded;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/*
创建嵌套对象有些时候, 在数据库逻辑中, 你想将一个实体或者POJO表示为一个紧密联系的整体,
即使这个对象包含几个域. 在这些情况下, 你能够使用@Embedded注解来表示一个对象,
而你想将这个对象分解为表内的子域. 然后你可以查询这些嵌套域, 就像你查询其它的独立列一样.
这个表表示UserInfo对象包含如下几列: id, firstName, street, state, city和post_code.
备注: 嵌套的域同样可以包含其它的嵌套域.
如果实体拥有多个相同类型的嵌套域, 你可以通过设置prefix属性保留每一列唯一. 然后Room给嵌套对象的每一个列名的起始处添加prefix设置的给定值
*/
@Entity
public class UserInfo {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code")
public int postCode;
}
类型转换器
package com.loaderman.roomdemo;
import androidx.room.TypeConverter;
import java.sql.Date;
//类型转换器
public class Converters {
@TypeConverter
public static Date revertDate(long value) {
return new Date(value);
}
@TypeConverter
public static long converterDate(Date value) {
return value.getTime();
}
}
数据库迁移
你添加和更改App功能时,你需要修改实体类来反映这些更改。当用户更新到你的应用最新版本时,你不想要他们丢失所有存在的数据,尤其是你无法从远端服务器恢复数据时。
Room
允许你编写Migration类来保留用户数据。每个Migration
类指明一个startVersion
和endVersion
。在运行时,Room
运行每个Migration
类的migrate()
方法,使用正确的顺序来迁移数据库到最新版本。
警告:如果你没有提供需要的迁移类,Room
将会重建数据库,也就意味着你会丢掉数据库中的所有数据。
警告:为了使迁移逻辑正常运行,请使用完整查询,而不是引用代表查询的常量。
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE user ADD COLUMN phone TEXT ");
}
};
配置数据库:
package com.loaderman.roomdemo;
import android.app.Application;
import androidx.room.Room;
import com.loaderman.roomdemo.dao.AppDatabase;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initDb();
}
private static AppDatabase db;
private void initDb() {
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "UserDB")
//添加数据库的变动迁移支持(当前状态从version1到version2的变动处理)
//数据库升级1-->2
.addMigrations(AppDatabase.MIGRATION_1_2)
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
//他可能造成主线程lock以及anr
// .allowMainThreadQueries()
.build();
}
public static AppDatabase getAppDbHelper() {
return db;
}
}
记录数据库
在主module的build.gradle里面添加代码即可
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//room的数据库概要、记录
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
sourceSets {
//数据库概要、记录存放位置
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
效果:
测试:
package com.loaderman.roomdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.loaderman.roomdemo.bean.User;
import com.loaderman.roomdemo.dao.UserDao;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends AppCompatActivity {
UserDao userDao;
private TextView tvMsg;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userDao = MyApplication.getAppDbHelper().getUserDao();
tvMsg = findViewById(R.id.tv_msg);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_insert_one:
System.out.println("---------btn_insert_one----------------");
new Thread(new Runnable() {
@Override
public void run() {
long insert = userDao.insert(new User(33,"张三", 18));
System.out.println(insert+"loaderman");
System.out.println("-------------------------");
MainActivity.this.setMsg("插入的主键值:" + insert);
}
}).start();;
break;
case R.id.btn_insert_multiple:
new Thread(new Runnable() {
@Override
public void run() {
List<User> list = new ArrayList<>();
list.add(new User(44,"李四", 42));
list.add(new User(55,"王五", 56));
List<Long> insertALl = userDao.insert(list);
MainActivity.this.setMsg("插入的主键值:" + insertALl.toString());
}
}).start();;
break;
case R.id.btn_query_one:
new Thread(new Runnable() {
@Override
public void run() {
User user1 = userDao.getUser("张三");
if (user1==null){
MainActivity.this.setMsg("没有查询到" );
}else {
MainActivity.this.setMsg("查询单个:" + user1.toString());
}
}
}).start();;
break;
case R.id.btn_query_multiple:
new Thread(new Runnable() {
@Override
public void run() {
List<User> allUsers = userDao.getAllUsers();
if (allUsers==null){
MainActivity.this.setMsg("没有查询到" );
}else {
MainActivity.this.setMsg("查询多个:" + allUsers.toString());
}
}
}).start();;
break;
case R.id.btn_update_one:
new Thread(new Runnable() {
@Override
public void run() {
int update = userDao.update(new User(33,"张三", 25));
MainActivity.this.setMsg("更新行数:" + update);
}
}).start();
break;
case R.id.btn_update_multiple:
new Thread(new Runnable() {
@Override
public void run() {
List<User> list3 = new ArrayList<>();
list3.add(new User("李四", 44));
list3.add(new User("王五", 55));
int updateAll = userDao.updateAll(list3);
MainActivity.this.setMsg("更新行数:" + updateAll);
}
}).start();;
break;
case R.id.btn_delete_one:
new Thread(new Runnable() {
@Override
public void run() {
int delete = userDao.delete(new User(33,"张三", 18));
MainActivity.this.setMsg("删除行数:" + delete);
}
}).start();;
break;
case R.id.btn_delete_multiple:
new Thread(new Runnable() {
@Override
public void run() {
List<User> list2 = new ArrayList<>();
list2.add(new User(44,"李四", 42));
list2.add(new User(55,"王五", 56));
int deleteAll = userDao.deleteAll(list2);
MainActivity.this.setMsg("删除行数:" + deleteAll);
}
}).start();;
break;
case R.id.btn_rx_query:
userDao.getUserByName("张三").observeOn(Schedulers.newThread()).subscribe(new Consumer<User>() {
@Override
public void accept(User user) {
//该回调是子线程。需要在主线程刷新UI
MainActivity.this.setMsg("RxJava查询:" + user.toString());
}
});
break;
case R.id.btn_live_query:
userDao.getAllUser().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
Toast.makeText(MainActivity.this,users.toString(),Toast.LENGTH_LONG).show();
tvMsg.setText(users.toString());
}
});
break;
}
}
private void setMsg(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,msg,Toast.LENGTH_LONG).show();
tvMsg.setText(msg);
}
});
}
}
多版本迁移
migrations:version 1 到 2, version 2 到 3, version 3 到 4
, 所以 Room 会一个接一个的触发所有 migration
。实际开发过程中,一般不会那么频繁posted on 2019-12-23 21:39 LoaderMan 阅读(2850) 评论(0) 编辑 收藏 举报