概述
最近打算研究一下Android的ORM框架据忘,即對(duì)象關(guān)系數(shù)據(jù)映射搞糕,ORM框架能很好的幫我們簡(jiǎn)化數(shù)據(jù)庫(kù)操作邏輯窍仰,增加開發(fā)效率,而且好的ORM還能幫我們?cè)黾訄?zhí)行效率针史。世面上有很多ORM框架碟狞,比如OrmLite、SugarORM频祝、GreenDAO竭业、ActiveAndroid、realm等,搜了一些資料進(jìn)行對(duì)比锯玛,感覺GreenDAO和Realm這兩款還是比較優(yōu)秀的,所以這里先介紹下GreenDAO和Realm的簡(jiǎn)單使用拙友。
GreenDAO
GreenDAO是基于Android原生數(shù)據(jù)庫(kù)框架進(jìn)行封裝的遗契,使我們不用寫復(fù)雜的SQL語句及很多的重復(fù)性語句。GreenDAO對(duì)Android進(jìn)行了高度優(yōu)化漾根,具有輕量級(jí)鲫竞、高性能、支持加密寄疏、支持protobuf僵井、對(duì)象激活批什、代碼自動(dòng)生成等特點(diǎn)。
- 配置依賴
引入GreenDAO需要分別在工程的build.gradle和Module的build.gradle進(jìn)行配置朋蔫。
工程的build.gradle:
buildscript {
repositories {
jcenter()
mavenCentral() // 添加這個(gè)倉(cāng)庫(kù)
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // 添加插件路徑
}
}
Module的build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // 添加插件的調(diào)用
dependencies {
implementation 'org.greenrobot:greendao:3.2.2' // 添加庫(kù)的依賴
}
- 配置數(shù)據(jù)庫(kù)版本號(hào)和代碼生成路徑
GreenDAO可以管理數(shù)據(jù)庫(kù)版本驯妄、自動(dòng)生成代碼合砂,需要我們通過配置告訴它數(shù)據(jù)庫(kù)版本號(hào)以及生成代碼的路徑翩伪,配置寫在Module的build.gradle,如下:
greendao {
schemaVersion 6 //數(shù)據(jù)庫(kù)版本號(hào)
targetGenDir "src/main/java" //生成代碼的根路徑
daoPackage "com.example.lenovo.dao" //在根路徑下生成類的包名
}
- 編寫Entity類
GreedDAO會(huì)根據(jù)注解自動(dòng)生成代碼凛剥,所以開始只需要編寫實(shí)體類即可轻姿,GreenDAO會(huì)幫我們自動(dòng)生成創(chuàng)建數(shù)據(jù)庫(kù)的代碼及各個(gè)表對(duì)應(yīng)的dao類逻炊。如下是一個(gè)Entity類:
@Entity
public class Student {
@Id(autoincrement = true) Long id;
String name;
int age;
String grades;
@ToMany(referencedJoinProperty = "studentId")
List<Course> courseList;
}
這就是個(gè)簡(jiǎn)單的數(shù)據(jù)類余素,只是增加了幾個(gè)注解炊昆,然后編譯一下工程凤巨,神奇的事情就會(huì)發(fā)生,這個(gè)類會(huì)擴(kuò)張成一個(gè)比較復(fù)雜的類炊林,其中Course是另一個(gè)類似的實(shí)體類,這里不再貼出代碼了。擴(kuò)張后的Student類如下:
@Entity
public class Student {
@Id(autoincrement = true) Long id;
String name;
int age;
String grades;
@ToMany(referencedJoinProperty = "studentId")
List<Course> courseList;
/** Used to resolve relations */
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/** Used for active entity operations. */
@Generated(hash = 1943931642)
private transient StudentDao myDao;
@Generated(hash = 990890750)
public Student(Long id, String name, int age, String grades) {
this.id = id;
this.name = name;
this.age = age;
this.grades = grades;
}
@Generated(hash = 1556870573)
public Student() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getGrades() {
return this.grades;
}
public void setGrades(String grades) {
this.grades = grades;
}
/**
* To-many relationship, resolved on first access (and after reset).
* Changes to to-many relations are not persisted, make changes to the target entity.
*/
@Generated(hash = 838351874)
public List<Course> getCourseList() {
if (courseList == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
CourseDao targetDao = daoSession.getCourseDao();
List<Course> courseListNew = targetDao._queryStudent_CourseList(id);
synchronized (this) {
if (courseList == null) {
courseList = courseListNew;
}
}
}
return courseList;
}
/** Resets a to-many relationship, making the next get call to query for a fresh result. */
@Generated(hash = 829241409)
public synchronized void resetCourseList() {
courseList = null;
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 128553479)
public void delete() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.delete(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 1942392019)
public void refresh() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.refresh(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 713229351)
public void update() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.update(this);
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 1701634981)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getStudentDao() : null;
}
}
可以看到自動(dòng)生成了很多代碼,包括構(gòu)造函數(shù)、get隘道、set郎笆、增刪改查方法等宛蚓。這些代碼就是GreenDAO根據(jù)我們寫的那幾個(gè)注解生成的。
@Entity表示這個(gè)類會(huì)生成一個(gè)對(duì)應(yīng)的表远舅,這個(gè)注解還有一些屬性可以設(shè)置:
@Entity(
schema = "schemaName", //表示實(shí)體類對(duì)應(yīng)的表
active = true, //表示實(shí)體類是“活的”痕钢,具有增刪改查
nameInDb = "AWESOME_USERS", //數(shù)據(jù)庫(kù)中使用的別名
indexes = { //定義索引
@Index(value = "name DESC", unique = true)
},
createInDb = true, //是否生成表蚤吹,默認(rèn)true
generateConstructors = true, //是否生成構(gòu)造方法
generateGettersSetters = true //是否生成get距辆、set方法
)
@Id(autoincrement = true) 表示表的主鍵暮刃,并且自動(dòng)增加
@ToMany(referencedJoinProperty = "studentId") 表示一個(gè)一對(duì)多的關(guān)系椭懊,并通過studentId關(guān)聯(lián)。
這類注解還有很多氧猬,比如
@Property(nameInDb="name") 可以配置一個(gè)非字段名的列盅抚,不配置默認(rèn)使用字段名
@NotNull 表示這列不能為空
@Transient 標(biāo)記的字段不會(huì)生成表的一列
@Unique 表示這列只能有唯一值
@ToOne 表示一對(duì)一關(guān)系 ,如@ToOne(joinProperty = "name")
@OrderBy 表示這列如何排序柱锹,當(dāng)get這列數(shù)據(jù)的時(shí)候會(huì)按照這個(gè)排序給出
再看一下之前配置的路徑src/main/java之下的com.example.lenovo.dao包下丰包,也生成了幾個(gè)類:
這個(gè)幾個(gè)類就是我們會(huì)用到的數(shù)據(jù)庫(kù)操作類邑彪。
其中的DaoMaster類有兩個(gè)內(nèi)部類寄症,OpenHelper和DevOpenHelper,如下
/**
* Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
*/
public static abstract class OpenHelper extends DatabaseOpenHelper {
public OpenHelper(Context context, String name) {
super(context, name, SCHEMA_VERSION);
}
public OpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory, SCHEMA_VERSION);
}
@Override
public void onCreate(Database db) {
Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
createAllTables(db, false);
}
}
/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name) {
super(context, name);
}
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
后者是前者子類释漆,在OpenHelper的onCreate方法中進(jìn)行了數(shù)據(jù)庫(kù)創(chuàng)建灵汪,而DevOpenHelper的onUpdate方法中是刪除數(shù)據(jù)庫(kù)并重新創(chuàng)建柑潦,每次升級(jí)數(shù)據(jù)庫(kù)就會(huì)調(diào)用這個(gè)方法,所以實(shí)際開發(fā)中我們可以自己實(shí)現(xiàn)一個(gè)OpenHelper览露,并在onUpdate方法中處理數(shù)據(jù)庫(kù)升級(jí)差牛。
- 初始化
有了上面生成的幾個(gè)類后,就可以進(jìn)行初始化工作了偏化,一般是在Application的onCreate方法中進(jìn)行侦讨,如下代碼:
public class MyApplication extends Application {
private DaoSession daoSession;
@Override
public void onCreate() {
super.onCreate();
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "test.db");
SQLiteDatabase database = devOpenHelper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(database);
daoSession = daoMaster.newSession();
}
public DaoSession getDaoSession(){
return daoSession;
}
}
這里通過OpenHelper生成了一個(gè)名為test的數(shù)據(jù)庫(kù)韵卤,并最終通過DaoMaster獲得了一個(gè)DaoSession保存在了Application中。
- 增刪改查
我們對(duì)數(shù)據(jù)表的操作都是通過對(duì)應(yīng)的dao類需忿,dao類可以通過Application中的DaoSession獲得蜡歹。
StudentDao studentDao = ((MyApplication)getApplication()).getDaoSession().getStudentDao();
//增
studentDao.insert(student); //插入一條數(shù)據(jù)
studentDao.insertOrReplace(student); //增加一條數(shù)據(jù),如果有這條數(shù)據(jù)則更新
//刪
studentDao.delete(student);
studentDao.deleteByKey(1L);
//改
studentDao.update(student);
//查
List<Student> studentList = studentDao.loadAll(); //獲取所有數(shù)據(jù)
studentDao.queryRaw("where age>?","18"); //直接寫查詢條件
studentDao.loadByRowId(1); //根據(jù)ID查詢
QueryBuilder builder = userDao.queryBuilder();
return builder.where(StudentDao.Properties.Age.gt(18)).build().list(); //通過QueryBuilder拼查詢條件
Realm
Realm和GreenDAO有很大的不同擅这,Realm本身就是一種數(shù)據(jù)庫(kù)仲翎,而且是可以支持跨平臺(tái)的數(shù)據(jù)庫(kù)铛漓,比SQLite更輕量級(jí),速度也更快玫坛,支持JSON格式湿镀、數(shù)據(jù)變更通知伐憾、數(shù)據(jù)同步等等特性。下面介紹它的簡(jiǎn)單使用流程蒸矛。
- 引入依賴
Realm同樣需要在工程的build.gradle文件和Module的build.gradle文件配置依賴。
工程的build.gradle:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "io.realm:realm-gradle-plugin:5.0.0" //realm插件路徑
}
}
Module中的build.gradle只需引入插件即可:
apply plugin: 'realm-android'
- 初始化
Realm的初始化也推薦在Application中進(jìn)行斩祭,例子如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
RealmConfiguration configuration = new RealmConfiguration
.Builder()
.name("test.realm") //設(shè)置數(shù)據(jù)庫(kù)名稱
.migration(new RealmMigration() { //設(shè)置升級(jí)策略
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
switch ((int) oldVersion){
case 0:
schema.create("Person")
.addField("name", String.class)
.addField("age", int.class);
oldVersion++;
case 1:
schema.get("Person")
.addField("id", long.class, FieldAttribute.PRIMARY_KEY)
.addRealmObjectField("favoriteDog", schema.get("Dog"))
.addRealmListField("dogs", schema.get("Dog"));
oldVersion++;
}
}
})
.deleteRealmIfMigrationNeeded() //合并需要?jiǎng)h除可以刪除
.schemaVersion(4) //設(shè)置版本號(hào)
.inMemory() //設(shè)置為內(nèi)存數(shù)據(jù)庫(kù)
.readOnly() //設(shè)置數(shù)據(jù)庫(kù)只讀
.build();
Realm.setDefaultConfiguration(configuration);
}
}
首先調(diào)用Realm.init(this),如果需要設(shè)置一些初始化功能蚊伞,可以創(chuàng)建一個(gè)RealmConfiguration时迫,代碼中可以看到谓晌,RealmConfiguration可以指定很多功能,比如數(shù)據(jù)庫(kù)名稱溺欧、升級(jí)策略柏肪、版本號(hào)烦味、以及設(shè)置只讀、內(nèi)存數(shù)據(jù)庫(kù)等等柏靶。然后把這個(gè)RealmConfiguration設(shè)置為Realm的默認(rèn)配置即可溃论。
- 編寫Entity類
Realm的實(shí)體類需要繼承RealmObject,也有一些功能性的注解可以使用炬转,如下是一個(gè)實(shí)體類:
public class Student extends RealmObject{
@PrimaryKey
long id;
String name;
int age;
String grades;
RealmList<Course> courses;
public RealmList<Course> getCourses() {
return courses;
}
public void setCourses(RealmList<Course> courses) {
this.courses = courses;
}
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 getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGrades() {
return grades;
}
public void setGrades(String grades) {
this.grades = grades;
}
}
其中@PrimaryKey表示主鍵扼劈,但是沒有自動(dòng)增加的功能乎婿,需要自己實(shí)現(xiàn)。學(xué)生和課程關(guān)系是用RealmList表示的捍靠,這種關(guān)系和SQLite中的一對(duì)多榨婆,多對(duì)多有可能是不太一樣的,但還需要進(jìn)一步研究才能確定谊迄。Course類也是類似的烟央,不再貼代碼了。
- 增刪改查
Realm的增刪改查是通過Realm類的實(shí)例來實(shí)現(xiàn)的粮呢,并且必須在事務(wù)中進(jìn)行而且不能跨線程操作啄寡,先看代碼:
Realm realm = Realm.getDefaultInstance(); //獲取默認(rèn)配置的Realm實(shí)例
//或者這樣隨時(shí)用一個(gè)RealmConfiguration參數(shù)生成一個(gè)Realm
//realm = Realm.getInstance(new RealmConfiguration.Builder().name("othor.realm").build());
//增
//增加3個(gè)課程
realm.beginTransaction(); //必須先開事務(wù)
List<Course> courseList = new ArrayList<>();
Course course1 = realm.createObject(Course.class); //使用createObject方法創(chuàng)建對(duì)象
course1.setName("數(shù)學(xué)");
course1.setId(0);
courseList.add(course1);
Course course2 = new Course(); //用new創(chuàng)建對(duì)象哩照,這個(gè)對(duì)象對(duì)于Realm是非托管的
course2.setName("語文");
course2.setId(1);
realm.copyToRealm(course2); //new 出的對(duì)象必須使用copyToRealm方法“拷貝”給Realm飘弧,不能直接插入,這也是個(gè)值得研究的問題
courseList.add(course2);
Course course3 = realm.createObject(Course.class);
course3.setName("英語");
course3.setId(2);
courseList.add(course3);
realm.insert(courseList); //批量插入
realm.commitTransaction(); //關(guān)閉事務(wù)
//或者用回調(diào)的方式操作蹋岩,可以代替事務(wù)開關(guān)
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Student student = new Student();
student.setName("小明");
student.setAge(18);
student.setGrades("一年級(jí)");
realm.copyToRealm(student);
}
});
//刪
realm.beginTransaction();
RealmResults<Student> students = realm.where(Student.class).findAll();
students.get(0).deleteFromRealm(); //通過實(shí)體類自己的方法刪除
students.deleteFirstFromRealm(); //通過RealmResults刪除
students.deleteLastFromRealm(); //通過RealmResults刪除
students.deleteFromRealm(1); //通過RealmResults刪除
realm.commitTransaction();
//改
realm.beginTransaction();
RealmResults<Student> students = realm.where(Student.class).findAll();
students.get(0).setName("更新名字"); //直接查出一個(gè)數(shù)據(jù)修改就行
realm.commitTransaction();
//查
//查的方式很多剪个,比如上面的findAll方法版确,還有下面的equalTo绒疗、between,查詢某個(gè)字段等于某個(gè)值或在某個(gè)區(qū)間等等惕虑,這種方法有很多很多
Student student = realm.where(Student.class).equalTo("age",18).findFirst();
RealmQuery<Student> studentList = realm.where(Student.class).between("age",10,18);
如上就是GreenDAO和Realm的簡(jiǎn)單使用流程,更深入的分析健提,可以關(guān)注后續(xù)文章伟叛。