今天要说的是众所周知的greenddao,之前一直都是手动sql,不仅在操作数据库的时候比较麻烦,而且还容易出错,朋友推荐了greendao这个轻量级的数据库框架。废话不多说,进入正题!
greendao的构建,是参见这篇文章:greenDao构建
还是把这步骤也说一下,方便大家也方便自己。
studio用户添加依赖:
compile 'de.greenrobot:greendao:1.3.7'
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/java-gen']
}
}
2.新建「GREENDAO GENERATOR」模块 (纯 JAVA 工程)
1)通过 File -> New -> New Module -> Java Library -> 填写相应的包名与类名 -> Finish.(注意此处是javalibrary)
2)在新创建的module的gradle中,配置 daoexamplegenerator 工程的 build.gradle,添加 dependencies.
compile 'de.greenrobot:greendao-generator:1.3.1'
3.编写ExampleDaoGenerator 这个类:
关于这个类的编写,直接上代码,注释详细:
首先贴出main方法:
public static void main(String [] args) throws Exception {
//注意导包
// 正如你所见的,你创建了一个用于添加实体(Entity)的模式(Schema)对象。
// 两个参数分别代表:数据库版本号与自动生成代码的包路径。
// Schema schema = new Schema(1, "me.itangqi.greendao");
// 当然,如果你愿意,你也可以分别指定生成的 Bean 与 DAO 类所在的目录,只要如下所示:
Schema schema = new Schema(2, "me.itangqi.bean");
schema.setDefaultJavaPackageDao("me.itangqi.dao");
// 模式(Schema)同时也拥有两个默认的 flags,分别用来标示 entity 是否是 activie 以及是否使用 keep sections。
// schema2.enableActiveEntitiesByDefault();
// schema2.enableKeepSectionsByDefault();
// 一旦你拥有了一个 Schema 对象后,你便可以使用它添加实体(Entities)了。
addNote(schema);
addPerson(schema);
// 最后我们将使用 DAOGenerator 类的 generateAll() 方法自动生成代码,此处你需要根据自己的情况更改输出目录(既之前创建的 java-gen)。
// 其实,输出目录的路径可以在 build.gradle 中设置,有兴趣的朋友可以自行搜索,这里就不再详解。
new DaoGenerator().generateAll(schema, "D:/StudioProjects/GreenDaoDemo/app/src/main/java-gen");
}
private static void addPerson(Schema schema) {
Entity person=schema.addEntity("Person");
person.addIdProperty();
person.addStringProperty("name").notNull();
person.addIntProperty("sex");
person.addIntProperty("grade");
person.addLongProperty("birthTime");
person.addIntProperty("age");
}
private static void addNote(Schema schema) {
// 一个实体(类)就关联到数据库中的一张表,此处表名为「Note」(既类名)
Entity note = schema.addEntity("Note");
// 你也可以重新给表命名
// note.setTableName("NODE");
// greenDAO 会自动根据实体类的属性值来创建表字段,并赋予默认值
// 接下来你便可以设置表中的字段:
note.addIdProperty();
note.addStringProperty("text").notNull();
// 与在 Java 中使用驼峰命名法不同,默认数据库中的命名是使用大写和下划线来分割单词的。
// For example, a property called “creationDate” will become a database column “CREATION_DATE”.
note.addStringProperty("comment");
note.addDateProperty("date");
}
在此需要注意的时,在做数据库升级的时候需要修改这里的版本号:
Schema schema = new Schema(2, "me.itangqi.bean"); //数据库这里修改版本号
最后执行此java文件,及可生成对应的xxxDao,xxx实体。
二、CRUD操作:
首先oncreate中初始化:
private void setupDatabase() {
// 通过 DaoMaster 的内部类 DevOpenHelper,你可以得到一个便利的 SQLiteOpenHelper 对象。
// 可能你已经注意到了,你并不需要去编写「CREATE TABLE」这样的 SQL 语句,因为 greenDAO 已经帮你做了。
// 注意:默认的 DaoMaster.DevOpenHelper 会在数据库升级时,删除所有的表,意味着这将导致数据的丢失。
// 所以,在正式的项目中,你还应该做一层封装,来实现数据库的安全升级。
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test.db", null);
db = helper.getWritableDatabase();
// 注意:该数据库连接属于 DaoMaster,所以多个 Session 指的是相同的数据库连接。
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
}
获取你需要操作的表XXXdao
daoSession.getPersonDao().insert(person);
下面来说说CRUD方法,以及常用的实现方式:
插入:
private long add(){
long age=3000;
Person person=new Person(null,"张大",1,age,18);
//添加一些对象
// daoSession.getPersonDao().insertInTx(persons);
//添加单个对象
// daoSession.getPersonDao().insert(person);
return daoSession.getPersonDao().insert(person);
}
更新:
private void update(){
//按照某一条件更新数据
Person person = persons.get(persons.size() - 1);
person.setName("张大大");
daoSession.getPersonDao().update(person);
//批量更新
/*public void updateBatch(List notes) throws Exception {
daoSession.getNoteDao().updateInTx(notes);
}*/
}
private void delete(){
//根据条件删除?
daoSession.getPersonDao().delete(persons.get(0));
Long key = daoSession.getPersonDao().getKey(persons.get(persons.size() - 1));
Log.e("key=",""+key);
/* //删除全部1
daoSession.deleteAll(Person.class);
//删除全部2
daoSession.getPersonDao().deleteAll();*/
/*//批量删除
public void deleteBatch(List notes) {
daoSession.getNoteDao().deleteInTx(notes);
}
// 批量按主键删除
public void deleteBatchByKey(List pkIds) {
daoSession.getNoteDao().deleteByKeyInTx(pkIds);
}*/
//sql删除
// daoSession.getDatabase().execSQL(deletesql);
}
private void query(){
//根据条件查询
// Query build = daoSession.getPersonDao().queryBuilder()
// .where(PersonDao.Properties.Sex.eq(1))
// .build();
// PersonDao.Properties.Age.gt() 大于 lt小于
// PersonDao.Properties.Age.ge() 大于等于 le小于等于
// PersonDao.Properties.Name.eq()
// PersonDao.Properties.Name.notEq("")//不等于
// List list=new ArrayList<>();
// list.add(1);
// list.add(2);
// PersonDao.Properties.Age.in(list); 查询条件在集合中有
// PersonDao.Properties.Name.between(1,5); 在某个区间
// PersonDao.Properties.Name.isNull(); 为null
// PersonDao.Properties.Name.like("%g"); 以g结尾 _g%
// PersonDao.Properties.Name.primaryKey 判断是否是主键
// QueryBuilder builder = daoSession.getPersonDao().queryBuilder();
// builder.where(PersonDao.Properties.Age.gt(0))
// .limit(2)//取的条数
// .offset(0) //开始位置
// .orderAsc(PersonDao.Properties.Age);//升序
// List list = builder.build().list();
List list = daoSession.getPersonDao().queryRaw("where "+PersonDao.Properties.Age.columnName + " >= ? and " + PersonDao.Properties.Sex.columnName + " = ?", new String[]{"2", "0"});
// daoSession.getDatabase().execSQL();
for (Person person:list
) {
Log.e("条件查询",person.toString());
}
}
查询全部:
private void queryAll(){
//查询全部的第一种方式
/* Query build = daoSession.getPersonDao().queryBuilder().build();
persons.clear();
persons.addAll(build.list());
for (Person person:persons
) {
Log.e("查询全部",person.toString());
}*/
//查询全部的第二种方式
/*List p = daoSession.getPersonDao().loadAll();
for (Person person:p
) {
Log.e("查询全部",person.toString());
}*/
//查询全部的第三种方式
/*List p = daoSession.loadAll(Person.class);
for (Person person:p
) {
Log.e("查询全部",person.toString());
}*/
//查询全部的第四种方式
List p = daoSession.queryBuilder(Person.class).build().list();
for (Person person:p
) {
Log.e("查询全部",person.toString());
persons.add(person);
}
}
greendao中没有提供全面的api根据某一条件进行,更新或者删除等,如需要可按照原生的数据库操作方式进行操作:
daoSession.getDatabase().delete()
三、数据库的升级:
查看DaoMaster中的升级方法,你会发现,在做数据库升级的方法中是删除所有的数据,然后在创建表,那么这样就无疑会导致数据库中的原有数据丢失。
/** * WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
在网上查看了一些提供的数据库升级方法,感觉都是很复杂或者不可用,下面介绍一种可用简单的数据库升级方式,此方法也是墙外找的,嘿嘿,还是拿来主义,其思路与一般数据库升级也一样,创建一个临时的表,将原来的数据保存到临时的表中,在删除原来的表,创建新表把临时表中的数据迁移到新的结构表中,删除临时表的过程。下面首先看看一个辅助类MigrationHelper:
package com.choe.greendaodemo.utils;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.internal.DaoConfig;
import me.itangqi.dao.DaoMaster;
/**
* Created by cyk
*/
public class MigrationHelper {
private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
private static MigrationHelper instance;
public static MigrationHelper getInstance() {
if(instance == null) {
instance = new MigrationHelper();
}
return instance;
}
public void migrate(SQLiteDatabase db, Class extends AbstractDao, ?>>... daoClasses) {
generateTempTables(db, daoClasses);
DaoMaster.dropAllTables(db, true);
DaoMaster.createAllTables(db, false);
restoreData(db, daoClasses);
}
/**
* 生成临时列表
* @param db
* @param daoClasses
*/
private void generateTempTables(SQLiteDatabase db, Class extends AbstractDao, ?>>... daoClasses) {
for(int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String divider = "";
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList properties = new ArrayList<>();
StringBuilder createTableStringBuilder = new StringBuilder();
createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
for(int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if(getColumns(db, tableName).contains(columnName)) {
properties.add(columnName);
String type = null;
try {
type = getTypeByClass(daoConfig.properties[j].type);
} catch (Exception exception) {
exception.printStackTrace();
}
createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);
if(daoConfig.properties[j].primaryKey) {
createTableStringBuilder.append(" PRIMARY KEY");
}
divider = ",";
}
}
createTableStringBuilder.append(");");
db.execSQL(createTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
/**
* 存储新的数据库表 以及数据
* @param db
* @param daoClasses
*/
private void restoreData(SQLiteDatabase db, Class extends AbstractDao, ?>>... daoClasses) {
for(int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList properties = new ArrayList();
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if(getColumns(db, tempTableName).contains(columnName)) {
properties.add(columnName);
}
}
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(insertTableStringBuilder.toString());
db.execSQL(dropTableStringBuilder.toString());
}
}
private String getTypeByClass(Class> type) throws Exception {
if(type.equals(String.class)) {
return "TEXT";
}
if(type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
return "INTEGER";
}
if(type.equals(Boolean.class)) {
return "BOOLEAN";
}
Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
exception.printStackTrace();
throw exception;
}
private static List getColumns(SQLiteDatabase db, String tableName) {
List columns = new ArrayList<>();
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
if (cursor != null) {
columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return columns;
}
}
然后在DaoMaster中进行调用:
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by migrating all tables data");
MigrationHelper.getInstance().migrate(db,
UserDao.class,
ItemDao.class);
}
这样就完成了数据库的升级以及数据的迁移。这种方式是不是很简单粗暴? 但在做数据库升级的时候有几点需要注意,第一点也是上文中提到的需要修改数据库的版本号,然后重新运行gernerator这个java类,重新构建实体、构造方法等。第二因为每次运行gernerator方法,都会删除之前实体和dao文件,因此为了不影响app的使用,还需要自行手动的添加一个之前表格式的构造(如果你之前是按照构造方法创建实体的话)。第三,即使数据库表结构没有变动的表最好也在migrate中传进去,因为有人说会出现一些你想不到的错误,博主也没测试过!虽然理论上不会产生错误,但还是为了保险起见,都做一个变化吧!
最后最重要的一点是,修改onUpgrade方法中的代码,否则之前所做的都会前功尽弃,如果这样上线的话,还会导致数据的丢失。这点切记!!!
忘了说了,此处代码都是写在activity中,但博主建议写在application中!
Demo下载地址:
greendao测试代码