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

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页面:

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

 1 buildscript {
 2     repositories {
 3         mavenCentral()
 4     }
 5     dependencies {
 6         classpath 'org.greenrobot:greendao-gradle-plugin:3.1.0'
 7     }
 8 }
 9 //上面是在project的build.gradle文件中,下面是在app的build.gradle文件中,具体见后面实例
10 apply plugin: 'org.greenrobot.greendao'
11 
12 dependencies {
13     compile 'org.greenrobot:greendao:3.1.0'
14 }

  当然,人家也说了“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。

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


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

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

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

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

  https://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:

 1 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 2 
 3 buildscript {
 4     repositories {
 5         jcenter()
 6     }
 7     dependencies {
 8         classpath 'com.android.tools.build:gradle:2.1.0'
 9         classpath 'org.greenrobot:greendao-gradle-plugin:3.1.1'  //greendao
10 
11         // NOTE: Do not place your application dependencies here; they belong
12         // in the individual module build.gradle files
13     }
14 }
15 
16 allprojects {
17     repositories {
18         jcenter()
19     }
20 }
21 
22 task clean(type: Delete) {
23     delete rootProject.buildDir
24 }

app build.gradle文件:

 1 apply plugin: 'com.android.application'
 2 apply plugin: 'org.greenrobot.greendao'  //greendao
 3 
 4 android {
 5     compileSdkVersion 24
 6     buildToolsVersion "24.0.0"
 7 
 8     greendao{  //greendao
 9         schemaVersion 1
10         targetGenDir 'src/main/java'
11     }
12     defaultConfig {
13         applicationId "com.learn.greendaotest"
14         minSdkVersion 22
15         targetSdkVersion 24
16         versionCode 1
17         versionName "1.0"
18     }
19     buildTypes {
20         release {
21             minifyEnabled false
22             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23         }
24     }
25 }
26 
27 dependencies {
28     compile fileTree(dir: 'libs', include: ['*.jar'])
29     testCompile 'junit:junit:4.12'
30     compile 'com.android.support:appcompat-v7:24.0.0'
31     compile 'org.greenrobot:greendao:3.1.1'  //greendao
32 }

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

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

 1 package com.learn.greendaotest;
 2 
 3 import org.greenrobot.greendao.annotation.Entity;
 4 import org.greenrobot.greendao.annotation.Id;
 5 import org.greenrobot.greendao.annotation.Property;
 6 
 7 @Entity
 8 public class Student {
 9     @Id
10     private Long id;
11     @Property(nameInDb = "NAME")
12     private String name;
13     @Property(nameInDb = "AGE")
14     private int age;
15 }

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

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

 1 @Generated(hash = 352757281)
 2 public Student(Long id, String name, int age) {
 3     this.id = id;
 4     this.name = name;
 5     this.age = age;
 6 }
 7 @Generated(hash = 1556870573)
 8 public Student() {
 9 }
10 public Long getId() {
11     return this.id;
12 }
13 public void setId(Long id) {
14     this.id = id;
15 }
16 public String getName() {
17     return this.name;
18 }
19 public void setName(String name) {
20     this.name = name;
21 }
22 public int getAge() {
23     return this.age;
24 }
25 public void setAge(int age) {
26     this.age = age;
27 }

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

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

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

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

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

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

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

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

 1 @Override
 2 public Student readEntity(Cursor cursor, int offset) {
 3     Student entity = new Student( //
 4         cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
 5         cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // name
 6         cursor.getInt(offset + 2) // age
 7     );
 8     return entity;
 9 }
10  
11 @Override
12 public void readEntity(Cursor cursor, Student entity, int offset) {
13     entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
14     entity.setName(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
15     entity.setAge(cursor.getInt(offset + 2));
16  }

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

 1 public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
 2         daoConfigMap) {
 3     super(db);
 4 
 5     studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
 6     studentDaoConfig.initIdentityScope(type);
 7 
 8     studentDao = new StudentDao(studentDaoConfig, this);
 9 
10     registerDao(Student.class, studentDao);
11 }
12 
13 public StudentDao getStudentDao() {
14     return studentDao;
15 }

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

 1 /**
 2  * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
 3  */
 4 public static abstract class OpenHelper extends DatabaseOpenHelper {
 5     public OpenHelper(Context context, String name) {
 6         super(context, name, SCHEMA_VERSION);
 7     }
 8 
 9     public OpenHelper(Context context, String name, CursorFactory factory) {
10         super(context, name, factory, SCHEMA_VERSION);
11     }
12 
13     @Override
14     public void onCreate(Database db) {
15         Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
16         createAllTables(db, false);
17     }
18 }
19 
20 /** WARNING: Drops all table on Upgrade! Use only during development. */
21 public static class DevOpenHelper extends OpenHelper {
22     public DevOpenHelper(Context context, String name) {
23         super(context, name);
24     }
25 
26     public DevOpenHelper(Context context, String name, CursorFactory factory) {
27         super(context, name, factory);
28     }
29 
30     @Override
31     public void onUpgrade(Database db, int oldVersion, int newVersion) {
32         Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
33         dropAllTables(db, true);
34         onCreate(db);
35     }
36 }

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

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

  创建表也是如此:

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

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

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

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

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

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

1 protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;
2 
3 public AbstractDaoMaster(Database db, int schemaVersion) {
4     this.db = db;
5     this.schemaVersion = schemaVersion;
6 
7     daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
8 }

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

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

1 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类实例:

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

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

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

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

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

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

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类型参数值,适当的时候利用封装偷偷懒。

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

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

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

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

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

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 删除数据,过程和查询类似,只不过将符合条件的实体值删除而已。

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

  以上代码目的为删除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吧。

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

  注意,这里调用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:

1 greendao{  //greendao
2     schemaVersion 2
3     targetGenDir 'src/main/java'
4 }

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

1 @Id
2 private Long id;
3 @Property(nameInDb = "NAME")
4 private String name;
5 @Property(nameInDb = "AGE")
6 private int age;
7 @Property
8 private String sex;

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

 

3. 总结

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

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

 

posted @ 2016-08-29 16:48  路上的脚印  阅读(3919)  评论(0编辑  收藏  举报