首页 > 代码库 > greenDao:操作数据库的开源框架

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包的获取:http://files.cnblogs.com/files/tgyf/greendao-3.1.1.rar,下载后不用解压,直接将后缀改为“.jar”即可。

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

技术分享

  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:

 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‘  //greendao10 11         // NOTE: Do not place your application dependencies here; they belong12         // in the individual module build.gradle files13     }14 }15 16 allprojects {17     repositories {18         jcenter()19     }20 }21 22 task clean(type: Delete) {23     delete rootProject.buildDir24 }

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 110         targetGenDir ‘src/main/java‘11     }12     defaultConfig {13         applicationId "com.learn.greendaotest"14         minSdkVersion 2215         targetSdkVersion 2416         versionCode 117         versionName "1.0"18     }19     buildTypes {20         release {21             minifyEnabled false22             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‘  //greendao32 }

  和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     @Id10     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 @Override12 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     @Override14     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     @Override31     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 entity5  */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{  //greendao2     schemaVersion 23     targetGenDir ‘src/main/java‘4 }

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

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

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

 

3. 总结

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

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

 

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