是时候该放弃 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
。实际开发过程中,一般不会那么频繁