Android中集成了SQLite数据库,Android本身提供了完备的SDK提供访问存取SQLite的接口,通过SQLiteOpenHelper类来管理SQLite数据库。在SQLiteOpenHelper类中的onCreate方法中创建数据库,在onUpgrade方法中升级数据库。
但随着应用的复杂度越来越高,表设计中的字段越来越多,我们编写的SQL语句越来越长,CURD代码越来越冗余冗长,越来越难以维护,而且导致错误率上升,影响开发质量和效率。于是各种ORM的数据库框架应运而生。比如ORMLite、SugarORM、GreenDAO、Active Android、Realm等。其中很多基于注解的ORM实现,类似于Hibernate,但在Android设备上,从性能上分析并不可取。greenDAO未采用注解,而是使用代码生成器生成Entity来实现Java对象到数据库实例的映射,效率非常高。
greenDAO的github地址是https://github.com/greenrobot/greenDAO,官网http://greenrobot.org/greendao。
Android Studio中的集成过程:
1、在module的build.gradle中dependencies的添加
compile ‘de.greenrobot:greendao:2.0.0’
2、导入greenDAO的代码生成器模块:
我采用的方式是直接从github上clone整个greenDAO的项目,然后在自己的项目中导入需要的代码生成器模块,导入成功后如图:
3、使用生成器生成Entity,DAO,Master以及Session等greenDAO要用到的类
DaoGenerator和DAOExampleGenerator模块都是Java工程,可以直接右键运行,而且DAOExampleGenerator是依赖于DAOGenerator的。我们在DAOExampleGenerator模块中的方法中进行Java对象的生成:
public static void main(String[] args) throws Exception {
Schema schema = new Schema(1000, "com.idengpan.androidwidgte.db");
addNote(schema);
addCustomerOrder(schema);
new DaoGenerator().generateAll(schema, "E:\\2016\\GithubDemo\\AndroidWidget\\app\\src\\main\\java");
}
private static void addNote(Schema schema) {
Entity note = schema.addEntity("Note");
note.addIdProperty();
note.addStringProperty("text").notNull();
note.addStringProperty("comment");
note.addDateProperty("date");
}
private static void addCustomerOrder(Schema schema) {
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();
Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);
}
其中,com.idengpan.androidwidgte.db指这些类生成的包路径,E:\2016\GithubDemo\AndroidWidget\app\src\main\java指的是生成的类对应的Java文件的物理磁盘路径。实际操作过程中,发现它会自动生成包路径的目录,将类生成到E:\2016\GithubDemo\AndroidWidget\app\src\main\java\com\idengpan\androidwidgte\db目录中。生成完,基本上就可以操作了。
注意:这期间,我遇到一个问题,是template not found for name dao.ftl的异常,说是没找到dao.ftl的模板文件,其实这个是存在的。后来经过断点调试,才发现因为经过了locale后,在中文环境下它会寻找dao_zh_CN.ftl的文件,自然就有问题了。于是我将dao.ftl复制了一份,改名为dao_zh_CN.ftl,即解决了这个问题,正常生成了文件。
使用greenDAO
greenDAO操作的都是Java的实体对象,几乎无需单独写完整的SQL代码。我在自定义的Appilication中定义方法中定义获取DAOMaster和DAOSession,这样防止每次操作都要新建DAOMaster和DAOSession的实例。后面我觉得把Application当做单例类用,不太好,就新建了一个DBService,来获取DAOMaster和DAOSession。
public class MyApplication extends Application {
private static DaoMaster mDaoMaster;
private static DaoSession mDaoSession;
private static Context mApplicationContext;
@Override
public void onCreate() {
super.onCreate();
mApplicationContext = getApplicationContext();
}
public static DaoMaster getDaoMaster(){
if(mDaoMaster == null){
DaoMaster.OpenHelper openHelper = new DaoMaster.DevOpenHelper(mApplicationContext,"test-db",null);
mDaoMaster = new DaoMaster(openHelper.getWritableDatabase());
}
return mDaoMaster;
}
public static DaoSession getDaoSession(){
if(mDaoSession == null){
if(mDaoMaster == null){
getDaoMaster();
}
mDaoSession = mDaoMaster.newSession();
}
return mDaoSession;
}
}
插入
然后插入实例:
Note note = new Note();
note.setText("这是Note" + index++);
note.setDate(new Date());
note.setComment("这是备注" + index);
MyApplication.getDaoSession().insert(note);
通过看源码,发现greenDAO是开了事务的,也就是说如果批量插入1000条数据的话,就会使用1000个事务。这是很不科学的。greenDAO给出的方案是:
MyApplication.getDaoSession().runInTx(new Runnable() {
@Override
public void run() {
long current = System.currentTimeMillis();
for (int k = 0; k < 1000; k++) {
Note note = new Note();
note.setText("这是Note" + k);
note.setDate(new Date());
note.setComment("这是备注" + k);
MyApplication.getDaoSession().insert(note);
}
System.out.println("耗时:" + (System.currentTimeMillis() - current));
}
});
从测试结果是耗时62ms。效率还是很不错的!!测试几次,有时可以达到48ms,哈哈。如果不采用在事务中批量插入,而是每次插入都开启事务,那么1000条记录的插入耗时2710ms,平均都在2500ms以上,界面会出现卡顿,效率不高,不可取。
注意:这里的Runnable是运行在UI线程的,如果测试时发现事务耗时较长,需要使用Loader。
查询
List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq("Joe"))
.orderAsc(Properties.LastName)
.list();
QueryBuilder qb = userDao.queryBuilder();
qb.where(Properties.FirstName.eq("Joe"),
qb.or(Properties.YearOfBirth.gt(1970),
qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10))));
List youngJoes = qb.list();
更多的内容可以参考greenDAO的源码以及官网介绍。注意上述代码的符号,qb.or和qb.and是qb.where方法中的参数。
开发测试时可以打开下面的开关来进行调试:
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
删除
官网是这样描述的:
Bulk deletes do not delete individual entities, but all entities matching some criteria. To perform bulk deletes, create a QueryBuilder, call its buildDelete method, and execute the returned DeleteQuery. This part of the API may change in the future, e.g. convenience methods may be added etc. Keep in mind, that bulk deletes currently do not affect entities in the identity scope, e.g. you could “resurrect” deleted entities if they have been cached before and are accessed by their ID (load method). Consider clearing the identity scope for now, if that may cause issues for your use case.
调用删除也是使用QueryBuilder,也可以加查询条件,然后调用QueryBuilder的buildDelete方法,然后执行返回的DeleteQuery对象的方法,我看了下目前是executeDeleteWithoutDetachingEntities()方法。
里面也提到,这个删除的API未来可能会变更,比如会增加一些更方便使用的API。
于是批量删除的示例代码如下:
QueryBuilder qbDel = DBService.getDaoSession().queryBuilder(Note.class);
qbDel.where(NoteDao.Properties.Id.gt(1000));
DeleteQuery dq = qbDel.buildDelete();
dq.executeDeleteWithoutDetachingEntities();
如果是单个Entity的删除,就简单了,可以使用DAO或Session中的方法来执行delete方法,传入要删除的Entity即可。
而且它还是按id为key进行删除的,其它的字段不校验。于是使用下面的代码就可以删除id为10的Note对象。
Note note = new Note(10l,"测试delete文本 10","测试delete备注 10",new Date());
DBService.getDaoSession().delete(note);
更新
很简单:
Note note = new Note(10l,"测试update文本 10","测试update备注 10",new Date());
DBService.getDaoSession().update(note);