概述
最近打算研究一下Android的ORM框架,即对象关系数据映射,ORM框架能很好的帮我们简化数据库操作逻辑,增加开发效率,而且好的ORM还能帮我们增加执行效率。世面上有很多ORM框架,比如OrmLite、SugarORM、GreenDAO、ActiveAndroid、realm等,搜了一些资料进行对比,感觉GreenDAO和Realm这两款还是比较优秀的,所以这里先介绍下GreenDAO和Realm的简单使用。
GreenDAO
GreenDAO是基于Android原生数据库框架进行封装的,使我们不用写复杂的SQL语句及很多的重复性语句。GreenDAO对Android进行了高度优化,具有轻量级、高性能、支持加密、支持protobuf、对象激活、代码自动生成等特点。
- 配置依赖
引入GreenDAO需要分别在工程的build.gradle和Module的build.gradle进行配置。
工程的build.gradle:
buildscript {
repositories {
jcenter()
mavenCentral() // 添加这个仓库
}
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' // 添加插件的调用
dependencies {
implementation 'org.greenrobot:greendao:3.2.2' // 添加库的依赖
}
- 配置数据库版本号和代码生成路径
GreenDAO可以管理数据库版本、自动生成代码,需要我们通过配置告诉它数据库版本号以及生成代码的路径,配置写在Module的build.gradle,如下:
greendao {
schemaVersion 6 //数据库版本号
targetGenDir "src/main/java" //生成代码的根路径
daoPackage "com.example.lenovo.dao" //在根路径下生成类的包名
}
- 编写Entity类
GreedDAO会根据注解自动生成代码,所以开始只需要编写实体类即可,GreenDAO会帮我们自动生成创建数据库的代码及各个表对应的dao类。如下是一个Entity类:
@Entity
public class Student {
@Id(autoincrement = true) Long id;
String name;
int age;
String grades;
@ToMany(referencedJoinProperty = "studentId")
List<Course> courseList;
}
这就是个简单的数据类,只是增加了几个注解,然后编译一下工程,神奇的事情就会发生,这个类会扩张成一个比较复杂的类,其中Course是另一个类似的实体类,这里不再贴出代码了。扩张后的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;
}
}
可以看到自动生成了很多代码,包括构造函数、get、set、增删改查方法等。这些代码就是GreenDAO根据我们写的那几个注解生成的。
@Entity表示这个类会生成一个对应的表,这个注解还有一些属性可以设置:
@Entity(
schema = "schemaName", //表示实体类对应的表
active = true, //表示实体类是“活的”,具有增删改查
nameInDb = "AWESOME_USERS", //数据库中使用的别名
indexes = { //定义索引
@Index(value = "name DESC", unique = true)
},
createInDb = true, //是否生成表,默认true
generateConstructors = true, //是否生成构造方法
generateGettersSetters = true //是否生成get、set方法
)
@Id(autoincrement = true) 表示表的主键,并且自动增加
@ToMany(referencedJoinProperty = "studentId") 表示一个一对多的关系,并通过studentId关联。
这类注解还有很多,比如
@Property(nameInDb="name") 可以配置一个非字段名的列,不配置默认使用字段名
@NotNull 表示这列不能为空
@Transient 标记的字段不会生成表的一列
@Unique 表示这列只能有唯一值
@ToOne 表示一对一关系 ,如@ToOne(joinProperty = "name")
@OrderBy 表示这列如何排序,当get这列数据的时候会按照这个排序给出
再看一下之前配置的路径src/main/java之下的com.example.lenovo.dao包下,也生成了几个类:
这个几个类就是我们会用到的数据库操作类。
其中的DaoMaster类有两个内部类,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方法中进行了数据库创建,而DevOpenHelper的onUpdate方法中是删除数据库并重新创建,每次升级数据库就会调用这个方法,所以实际开发中我们可以自己实现一个OpenHelper,并在onUpdate方法中处理数据库升级。
- 初始化
有了上面生成的几个类后,就可以进行初始化工作了,一般是在Application的onCreate方法中进行,如下代码:
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生成了一个名为test的数据库,并最终通过DaoMaster获得了一个DaoSession保存在了Application中。
- 增删改查
我们使用数据表对应的dao类对表进行操作,dao类可以通过Application中的DaoSession获得。
StudentDao studentDao = ((MyApplication)getApplication()).getDaoSession().getStudentDao();
//增
studentDao.insert(student); //插入一条数据
studentDao.insertOrReplace(student); //增加一条数据,如果有这条数据则更新
//删
studentDao.delete(student); //通过对象删除
studentDao.deleteByKey(1L); //通过id删除
//改
studentDao.update(student);
//查
List<Student> studentList = studentDao.loadAll(); //获取所有数据
studentDao.queryRaw("where age>?","18"); //直接写查询条件
studentDao.loadByRowId(1); //根据ID查询
QueryBuilder builder = userDao.queryBuilder();
return builder.where(StudentDao.Properties.Age.gt(18)).build().list(); //通过QueryBuilder拼查询条件
Realm
Realm和GreenDAO有很大的不同,Realm本身就是一种数据库,而且是可以支持跨平台的数据库,比SQLite更轻量级,速度也更快,支持JSON格式、数据变更通知、数据同步等等特性。下面介绍它的简单使用流程。
- 引入依赖
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中进行,例子如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
RealmConfiguration configuration = new RealmConfiguration
.Builder()
.name("test.realm") //设置数据库名称
.migration(new RealmMigration() { //设置升级策略
@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() //合并需要删除可以删除
.schemaVersion(4) //设置版本号
.inMemory() //设置为内存数据库
.readOnly() //设置数据库只读
.build();
Realm.setDefaultConfiguration(configuration);
}
}
首先调用Realm.init(this),如果需要设置一些初始化功能,可以创建一个RealmConfiguration,代码中可以看到,RealmConfiguration可以指定很多功能,比如数据库名称、升级策略、版本号、以及设置只读、内存数据库等等。然后把这个RealmConfiguration设置为Realm的默认配置即可。
- 编写Entity类
Realm的实体类需要继承RealmObject,比GreenDAO侵入性要高,也有一些功能性的注解可以使用,但没有GreenDAO的注解强大,如下是一个实体类:
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表示主键,但是没有自动增加的功能,需要自己实现。学生和课程关系是用RealmList表示的,这种关系和SQLite中的一对多、多对多有可能是不太一样的,但还需要进一步研究才能确定。Course类也是类似的,不再贴代码了。
- 增删改查
Realm的增删改查是通过Realm类的实例来实现的,并且必须在事务中进行而且不能跨线程操作,先看代码:
Realm realm = Realm.getDefaultInstance(); //获取默认配置的Realm实例
//或者这样随时用一个RealmConfiguration参数生成一个Realm
//realm = Realm.getInstance(new RealmConfiguration.Builder().name("othor.realm").build());
//增
//增加3个课程
realm.beginTransaction(); //必须先开事务
List<Course> courseList = new ArrayList<>();
Course course1 = realm.createObject(Course.class); //使用createObject方法创建对象
course1.setName("数学");
course1.setId(0);
courseList.add(course1);
Course course2 = new Course(); //用new创建对象,这个对象对于Realm是非托管的
course2.setName("语文");
course2.setId(1);
realm.copyToRealm(course2); //new 出的对象必须使用copyToRealm方法“拷贝”给Realm,不能直接插入,这也是个值得研究的问题
courseList.add(course2);
Course course3 = realm.createObject(Course.class);
course3.setName("英语");
course3.setId(2);
courseList.add(course3);
realm.insert(courseList); //批量插入
realm.commitTransaction(); //关闭事务
//或者用回调的方式操作,可以代替事务开关
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Student student = new Student();
student.setName("小明");
student.setAge(18);
student.setGrades("一年级");
realm.copyToRealm(student);
}
});
//删
realm.beginTransaction();
RealmResults<Student> students = realm.where(Student.class).findAll();
students.get(0).deleteFromRealm(); //通过实体类自己的方法删除
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("更新名字"); //直接查出一个数据修改就行
realm.commitTransaction();
//查
//查的方式很多,比如上面的findAll方法,还有下面的equalTo、between,查询某个字段等于某个值或在某个区间等等,这种方法有很多很多
Student student = realm.where(Student.class).equalTo("age",18).findFirst();
RealmQuery<Student> studentList = realm.where(Student.class).between("age",10,18);
如上就是GreenDAO和Realm的简单对比及使用流程,更深入的分析,可以关注后续文章。