greenDao:操作数据库的开源框架

时间:2023-03-08 15:39:54

greenDAO: Android ORM for your SQLite database

1. greenDao库获取

  英文标题借鉴的是greendrobot官网介绍greenDao时给出的Title,链接:http://greenrobot.org/greendao/,有兴趣的可以点进去(不用外网),里面有使用说明及相关资料的下载接口。greenDao目前版本已经更新到3.x.x,与2.x.x相比支持的功能发生了很大的改变(后面会提到),所以强烈推荐使用最新的版本。如3.1.1版本的获取方法如下:

  a. 通过链接进入官网,点击右边的greenDao按钮进入其GitHub页面:

greenDao:操作数据库的开源框架

  b. greenDao GitHub页面上的“Add greenDAO to your project”模块是针对Android Studio开发者的引用库添加说明,在app和project build.gradle文件中分别编写以下代码并进行同步(Studio界面上有一个同步按钮,不会的自己查)就可以使用greenDao提供的方法来操作SQLite数据库了:

 buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.1.0'
}
}
//上面是在project的build.gradle文件中,下面是在app的build.gradle文件中,具体见后面实例
apply plugin: 'org.greenrobot.greendao' dependencies {
compile 'org.greenrobot:greendao:3.1.0'
}

  当然,人家也说了“Please ensure that you are using the latest versions by checking here and here”,即点进去瞧瞧版本更新到多少了,条件允许的话尽量用最新的。目前这两个链接分别对应版本3.1.1和3.1.0,如果在Studio中想用3.1,1,就将上述代码中的3.1.0改为3.1.1。

greenDao:操作数据库的开源框架

  c. 以版本3.1.1为例,点击checking here后会进入其资源下载页面(包括Eclipse开发者喜爱的jar包文件):

greenDao:操作数据库的开源框架

  如果使用的集成开发环境是Eclipse,那么会与Studio不同,在导入库方面还是较原始(需要自行下载jar包并添加到项目中,有些jar包难找的时候体会最深),点击图中的jar按钮即可开始下载。

  d. 想偷懒的可以直接点击链接进行jar包的获取:http://files.cnblogs.com/files/tgyf/greendao-3.1.1.rar,下载后不用解压,直接将后缀改为“.jar”即可。

  e. 注意,还需要用同样的方法下载jar包freemarker和greendao-generator,同样在上面的下载页面搜索就好:

greenDao:操作数据库的开源框架

  http://files.cnblogs.com/files/tgyf/freemarker-1.19.2.rar

  http://files.cnblogs.com/files/tgyf/greendao-generator-3.1.0.rar,下载后处理方式如上面红色字体描述——改后缀名"rar"->"jar"。

  这三个jar包的作用分别是:

  greendao——SQLite数据库操作核心,对一些常用的方法进行了封装;

  generator——根据需求表对应实体类生成相关的greendao类,如上面提到的DaoSession等;

  freemarker——将自动生成的类以文本形式输出;

  至于在开发中用哪个版本合适,视实际情况而定。能用Google亲生的Studio最好不过了,不用下载jar包,还有人家对eclipse已经不进行新特性的支持了。

2. greenDao操作SQLite数据库

  前面提到,新版本与老版本在支持的功能上有了很大的提升,最明显的地方就是gen目录下文件的生成时机或者说是方法。有老版本使用经验的小伙伴应该清楚,要想在android中利用greenDao相关类来处理SQLite数据库,必须先在另一个Java工程中建立需求表的实体类,生成gen目录下的文件(如xxxDao、DaoSession以及DaoMaster等),然后将gen目录拷入android工程中方能使用。当然,也可以将gen目录的生成路径直接定位到android工程中,免得每次修改实体类并重新编译后都需要拷贝这些文件。

  不过,这些问题在新版中就不存在了,因为实体类的定义与gen目录的生成都可以直接在android工程中完成。下面就来看看Studio中具体是怎么回事吧,至于对旧版本的用法感兴趣的自己去研究咯。

  2.1 在Studio中新建一个project greendaoTest,过程中根据需求设置各种属性,默认布局为显示一个字串“Hello World!”。其实如果只是对greenDao框架进行学习与测试,可以利用数据库查询工具或log打印内容来检测操作结果,不是必须将内容显示在布局组件中。

  2.2 在项目中添加库依赖代码,完整文件代码分别如下:

project build.gradle:

 // Top-level build file where you can add configuration options common to all sub-projects/modules.

 buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.1.1' //greendao // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
} allprojects {
repositories {
jcenter()
}
} task clean(type: Delete) {
delete rootProject.buildDir
}

app build.gradle文件:

 apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' //greendao android {
compileSdkVersion 24
buildToolsVersion "24.0.0" greendao{ //greendao
schemaVersion 1
targetGenDir 'src/main/java'
}
defaultConfig {
applicationId "com.learn.greendaotest"
minSdkVersion 22
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.0.0'
compile 'org.greenrobot:greendao:3.1.1' //greendao
}

  和greenDao相关的语句,末尾都加了注释“greenDao”,方便区分与维护。可以看出,这里导入的是最新的版本3.1.1。

  2.3 在与主类文件MainActivity.java同一目录下定义表——实体类Student,代码如下:

 package com.learn.greendaotest;

 import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Property; @Entity
public class Student {
@Id
private Long id;
@Property(nameInDb = "NAME")
private String name;
@Property(nameInDb = "AGE")
private int age;
}

  简单起见,声明了三个属性:id、name、age,分别表示学生的序号、名字、年龄,其中序号是唯一的。当然,如果需要,可以设置序号的顺序、属性的非空等限制条件。定义好实体类之后,只需要运行程序(或者点击build菜单中的make project/make module 'app'选项)就可以生成相关的greenDao操作类了。

  首先,仍旧看Student这个类,发现多了以下代码:

 @Generated(hash = 352757281)
public Student(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@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;
}

  代码并不陌生,带参数与不带参数的构造函数,以及三个属性的set/get方法,但是这些是自动生成的(包括接下来要讲的三个类,会贴出部分关键代码,所有的大家可以下载文末提供的源码)。

  表操作类StudentDao,可以说是greenDao生成的和Student类最亲近的一个类了,关键的地方有以下几个:

  a. 内部类——属性类Properties,关于属性Property,greenDao还提供了一系列好用的方法,后面会提到。

 public static class Properties {
public final static Property Id = new Property(0, Long.class, "id", true, "_id");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
public final static Property Age = new Property(2, int.class, "age", false, "AGE");
}

  b. 表创建与删除函数——createTable(Database db, boolean ifNotExists)和dropTable(Database db, boolean ifExists),操作时是否做表存在判断可以通过传入参数“ifExists”来决定。由于调用对象就是表对应的类本身,所以指定表所属数据库对象就好(不用关心表名是什么)。

 /** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
"\"_id\" INTEGER PRIMARY KEY ," + // 0: id
"\"NAME\" TEXT," + // 1: name
"\"AGE\" INTEGER NOT NULL );"); // 2: age
} /** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"STUDENT\"";
db.execSQL(sql);
}

  可以看出,自动生成的表创建代码将属性id、name、age类型设置为了数据库中的类型INTEGER、TEXT、INTEGER,id唯一PRIMARY KEY,age NOT NULL,而大写的名字是我们在定义Student类时自己声明的,如name:@Property(nameInDb = "NAME")。

  c. 实体对象获取方法,提供了两种方式:返回临时引用和直接给引用参数赋值,而后者对属性操作调用的是Student类中的set(Object object)方法。

 @Override
public Student readEntity(Cursor cursor, int offset) {
Student entity = new Student( //
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // name
cursor.getInt(offset + 2) // age
);
return entity;
} @Override
public void readEntity(Cursor cursor, Student entity, int offset) {
entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
entity.setName(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
entity.setAge(cursor.getInt(offset + 2));
}

  类DaoSession,简单点说就是用来获取类StudentDao实例的,留意其构造函数和实例返回方法即可。

 public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db); studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
studentDaoConfig.initIdentityScope(type); studentDao = new StudentDao(studentDaoConfig, this); registerDao(Student.class, studentDao);
} public StudentDao getStudentDao() {
return studentDao;
}

  类DaoMaster,是和org.greenrobot.greendao.database.DatabaseOpenHelper最亲近的类,那么到这里自定义的实体类Student和greenDao封装的数据库处理类终于联系上了。该类中又定义了两个静态内部类OpenHelper和DevOpenHelper,前者重载了类DatabaseOpenHelper的表建立方法onCreate(Database db),后者重载了表更新方法onUpgrade(Database db, int oldVersion, int newVersion)。

 /**
* 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);
}
}

  还定义了删除所有表的方法dropAllTables(Database db, boolean ifExists),由于我们之前只定义了一个实体类(表),所以这里只有StudentDao类删除表的语句。

/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
StudentDao.dropTable(db, ifExists);
}

  创建表也是如此:

 /** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
StudentDao.createTable(db, ifNotExists);
}

  以及和DaoSession类获取StudentSao实例相似的newDevSession(Context context, String name)方法,用以获取DaoSession类实例。

 public static DaoSession newDevSession(Context context, String name) {
Database db = new DevOpenHelper(context, name).getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
return daoMaster.newSession();
}

  而方法newSession()继而会调用DaoSession类的三参构造函数:

 public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}

  所以,不用担心获取DaoSession实例时该怎样传入参数,因为DaoMaster及其父类都已经完成了。如第三个参数daoConfigMap,在DaoMaster类中找不到,其父类AbstractDaoMaster完成了声明与初始化工作。

 protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;

 public AbstractDaoMaster(Database db, int schemaVersion) {
this.db = db;
this.schemaVersion = schemaVersion; daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
}

  2.4 准备工作差不多了,可以开始操作数据库了。由于只是进行简单的测试,所以不涉及界面,直接通过log打印的形式来描述结果了。

  2.4.1 建立数据库及实体类对应的表,第二个参数指定了数据库的名称,第三个参数(类型CursorFactory)一般为null即可。

 DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(getApplicationContext(), "student.db", null);

  结合之前给出的DaoMaster类代码,这句代码其实做了很多事情。执行DevOpenHelper类构造函数DevOpenHelper(Context context, String name, CursorFactory factory)时会调用OpenHelper类构造函数OpenHelper(Context context, String name, CursorFactory factory),进而又会调用基类DatabaseOpenHelper构造函数DatabaseOpenHelper(Context context, String name, CursorFactory factory, int version)与被重载方法onCreate(Database db),最后调用DaoMaster类的createAllTables(Database db, boolean ifNotExists)建立所有表。

  数据库与表格文件都建立好了,下面一步到位获取StudentDao类实例:

 DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb());
DaoSession daoSession = daoMaster.newSession();
StudentDao studentDao = daoSession.getStudentDao();

  2.4.2 插入两条数据,一般情况下第一个参数id是不需要特殊指定的(null即可),会自动从1开始增序填入表中。

 Student studentLl = new Student(null, "Lilei", 21);
Student studentHmm = new Student(null, "Hanmeimei", 21);
studentDao.insert(studentLl);
studentDao.insert(studentHmm);

  关于insert(T entity)方法,在AbstractDao类中的定义如下:

 /**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement(), true);
}

  再往下追踪能看得到不同层次的源码,这里不打算全部列一遍。关注一下该方法的最后一句注释:返回新插入实体在数据表中的行号,所以在对表中数据进行查询之前,可以通过打印返回值来判断插入是否成功。修改代码,添加返回值获取与打印:

long resultLl = studentDao.insert(studentLl);
long resultHmm = studentDao.insert(studentHmm);
showLog("row id of insert Lilei: "+resultLl);
showLog("row id of insert Hanmeimei: "+resultHmm);

  打印出的结果符合预期,表明插入成功了,因为之前已经执行过一次(row id为1和2),所以这里往后递增为3和4。还可以看出id是从1开始的,而不像熟悉的数组下标是从0开始。

08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Lilei: 3
08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Hanmeimei: 4

  showLog是什么鬼?自定义的一个用来打印log的方法,免得每次都需要传入TAG标记以及必须传入String类型参数值,适当的时候利用封装偷偷懒。

 private final String TAG = "STUDENT";
private void showLog(Object message) {
Log.i(TAG, ""+message);
}

  同样地,Toast提示语可以封装成:

private void showToast(Object message) {
Toast.makeText(MainActivity.this, ""+message, Toast.LENGTH_SHORT).show();
}

  2.4.3 数据查询,并将结果打印出来,看看是否真的插入了四个实体值。

 List<Student> listQuery = studentDao.queryBuilder().where(StudentDao.Properties.Id.between(1, 8)).limit(5).build().list();
for (Student student : listQuery) {
showLog("id: "+student.getId());
showLog("name: "+student.getName());
showLog("age: "+student.getAge());
}

08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: id: 1
08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 2
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 3
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21

  解释一下where(WhereCondition condition)和limit(int limit)这两个方法,其实看代码也能明白大概了。

  where():添加数据查询的筛选条件,除了这里的ID范围,还可以是字串包含或不包含某个字符/字串等;

  limit():只将查询结果中的前五条赋给List对象,其余的忽略;

  前面提到过,属性类Property有很多好用的方法。比如这里的between(Object value1, Object value2)限定值的区间,查看该类的定义还可以发现eq(Object value)方法是用来比较值是否相等,等等返回值为WhereCondition实例的方法,说它是为了where()方法而生也不为过。

  2.4.4 删除数据,过程和查询类似,只不过将符合条件的实体值删除而已。

 List<Student> listDelete = (List<Student>) studentDao.queryBuilder().where(StudentDao.Properties.Id.le(3)).build().list();
for (Student student : listDelete) {
studentDao.delete(student);
}

  以上代码目的为删除id小于等于3的行,那么再次输出结果只剩下一条数据了:

08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: age: 21

  2.4.5 更新数据,目前Student表中还剩下一条数据,那就将它的name字段给为Lilei吧。

 Student stu = studentDao.queryBuilder()
.where(StudentDao.Properties.Id.ge(4), StudentDao.Properties.Name.like("%mei%")).build().unique();
if (stu == null) {
showToast("实体不存在!");
}else{
stu.setName("Lilei");
studentDao.update(stu);
}

  注意,这里调用where()方法时传入了两个条件参数,其实还可以传更多。参数的意义分别为id大于等于4,name字段中间部分的值为“mei”。最后的unique()方法使结果唯一,即查询到一条马上停止查询过程并返回结果Student类实例,而不是List<Student>。如果没有找到则弹出Toast字串提醒用户,否则进行名字的修改。现在的查询结果就变成下面这样了:

08-29 04:08:48.788 10993-10993/? I/STUDENT: id: 4
08-29 04:08:48.788 10993-10993/? I/STUDENT: name: Lilei
08-29 04:08:48.788 10993-10993/? I/STUDENT: age: 21

  2.4.6 升级数据库,涉及到的重载方法onUpgrade(Database db, int oldVersion, int newVersion)已经在类DaoMaster中实现了,接下来需要做的是改变数据库版本与实体类属性。

  在app build.gradle文件将数据库版本值由1改为2:

 greendao{  //greendao
schemaVersion 2
targetGenDir 'src/main/java'
}

  在实体类Student中添加性别属性sex(String型),

 @Id
private Long id;
@Property(nameInDb = "NAME")
private String name;
@Property(nameInDb = "AGE")
private int age;
@Property
private String sex;

  接着执行程序即可。可以发现:性别属性没有指定其在StudentDao->Properties类中的别名(在数据表中的列名),而自动生成的属性别名为“SEX”。可知若没有显式声明,那么就按全大写来处理,否则按照指定的赋值,但是id除外(别名为_id)。

3. 总结

  本文中的测试案例都是很简单的情况,实际开发时需要处理的数据肯定要多得多。熟悉了greenDao框架的思想及用法之后,处理一般性的数据集时会简单、高效很多,通过面向对象的思想为实体类生成对应的操作类,结构清晰,易维护。

  最后给出项目下载链接:http://files.cnblogs.com/files/tgyf/greendaoTest.rar