GreenDAO开发经验总结
一、初识GreenDAO
1.1 什么是GreenDAO
greenDao是一个将对象映射到SQLite数据库中的轻量且快速的ORM解决方案。
GreenDAO官网有下面一张图来表示了以上概念:
而Dao呢,英文全称Date Access Object,它是一个数据访问接口,数据访问顾名思义就是与数据库打交道;它是夹在业务逻辑与数据库资源中间进行从关系型数据库到Java对象转换的媒介。
SQLite我们知道是一个安卓原生的一个轻量级的数据库,那么ORM是什么呢?
ORM的全名是Object Relational Mapping,即对象关系映射,简单的说就是对象模型和关系模型的一种映射。前面那句话看不懂也没有关系,其实简单点说就是数据库是一种关系型的,而我们使用的java呢是一种面向对象的编程形式,那么我们想要操作对象一样的操作数据库,ORM就是这样的一种关系到对象的映射,而我们的GreenDAO就是基于ORM解决方案的一种框架,让我们更简单的操作数据库。
ORM的优点:
1.让业务代码访问对象,而不是数据库表
2.隐藏了面向对象的逻辑SQL查询详情
3.无需处理数据库实现
1.2 选择GreenDAO原因
首先,我们的Android原生的api带来了很多不便,比如:
1.要手动拼装sql
2.要自己操作数据库的常规代码
3.不能自动把数据中的数据映射成对象
4.没有实现级联查询
其次,在GreenDAO的角度来看,又是怎样的呢?
1.性能优
2.开发文档比较全面
3.目前非常流行
3.使用方便
4.拓展性强
附:
1.官方网站:http://greenrobot.org/
2.GitHub实例:https://github.com/greenrobot/greenDAO/
3.GreenDAO属性中文参考文档:http://www.boyunjian.com/javadoc/de.greenrobot/greendao/1.3.1/_/de/greenrobot/dao/AbstractDao.html#readKey(Cursor,%20int)
二、操作GreenDAO
2.1 自动生成代码
通过查看GreenDAO的官方网站以及它在GitHub上的代码呢,发现在使用GreenDAO时,需要在java环境下自动生成一下GreenDAO的相关代码,然后再copy到安卓中来使用。自己主要是使用Eclipse来集成GreenDAO的。
2.1.1 下载相关jar包
2.1.2 新建java项目导入jar包
用eclipse新建一个java项目,取名DaoExampleGenerator,然后在java项目导入freemaker-2.3.10和greendao-generator-1.3.1两个jar包。新建的java项目主要目的是生成DaoMaster.java DaoSession.java Person.java PersonDao.java 这几个文件,将生成的文件放在Android项目中来使用。
2.1.3 新建Android工程
DaoExampleGenerator.java的代码下面再说,接下来新建一个android工程,这个android工程才是真正使用GreenDAO的地方。
建完android工程后,将jar包greendao-1.3.7放到android工程的libs目录下,工程的目录如下:
2.1.3 新建Android工程
DaoExampleGenerator.java的代码下面再说,接下来新建一个android工程,这个android工程才是真正使用GreenDAO的地方。
建完android工程后,将jar包greendao-1.3.7放到android工程的libs目录下,工程的目录如下:
2.1.4 DaoExampleGenerator.java
上面的DaoExampleGenerator.java中的代码都是greendao官网要求我们这样写的。运行新建的java工程,就会生成
这几个文件。通过日志输出可以看到生成的那些文件:
这就表示我们的代码自动生成成功了。接下来就是GreenDAO的使用。
2.2 GreenDAO的基本操作
2.2.1 GreenDAO核心类介绍:
GreenDAO有4个核心类,分别是:DaoMaster, OpenHelper, DaoSession, XXXDao。
DaoMaster:这个类是数据库的统领,也是整个操作的入口,其管理者数据库,数据库版本号和一些数据库相关信息。DaoMaster可以用来生成下面将要说到的DaoSession。在构造函数中需要对所有的DAO通过registerDaoClass()进行注册,以便DaoMaster对其进行管理。
OpenHelper: 根据官方文档,GreenDAO中总共有四种OpenHelper,分别是 OpenHelper, DevOpenHelper,EncryptedOpenHelper 和EncryptedDevOpenHelper;前两个是用来操作未加密数据库,后两个是用来操作加密数据库;DevOpenHelper 和 EncryptedDevOpenHelper 会在升级的时候删除所有表并重建,所以只建议在开发时使用。
OpenHelper的用来对数据库进行管理,如创建表,删除表,获取可读写数据库等。另外,OpenHelper还有对数据库创建,数据库升级和数据库开启等事件的监听,分别在onCreate(), onUpgrade(), onOpen()。
DaoSession: 管理所有的XXXDao, DaoSession中也会有增删改查的方法, 其可以直接通过要插入实体的类型找到对应的XXXDao后再进行操作。当没有找到实体对应的Dao时,会抛出 org.greenrobot.greendao.DaoException: No DAO registered for class XXX 的错误。
XXXDao: 对实体进行操作,有比DaoSession更丰富的操作,如loadAll, insertInTx.
2.2.2 Dao的初始化过程(也是GreenDAO创建数据库和表的过程)
这个过程比较复杂,首先看一下初始化dao的方法。
1 //创建数据库、表 2 private void openDb() { 3 /* 4 * 创建数据库:三个参数:参数1:上下文,参数2:库名,参数3:游标工厂 5 * 调用DaoMaster中的DevOpenHelper方法,DevOpenHelper方法继承自OpenHelper,而OpenHelper的父类为SQLiteOpenHelper。 6 * SQLiteOpenHelper主要用来创建数据库。该类的构造器中,调用Context中的方法创建并打开一个指 定名称的数据库对象。 7 * 如果打开的数据库版本号与当前的版本号不同则会调用ononUpgrade()方法,删除数据库中的所有表重新初始化调用onCreate(),在父类中完成表的创建。 8 */ 9 helper = new DaoMaster.DevOpenHelper(MainActivity.this, "person.db", 10 null); 11 //注意:该数据库连接属于DaoMaster,所以多个Session指的是相同的数据库连接。 12 //getWritableDatabase()方法是以读写的方式打开数据库 13 master = new DaoMaster(helper.getWritableDatabase()); 14 session = master.newSession(); 15 // 获得person表操作数据库类的对象 16 dao = session.getPersonDao(); 17 }
我这里最后返回的是dao。
上面的过程是固定不变的,由官方文档推荐使用,同时官方推荐将获取DaoMaster对象的放到Application层,这样将避免多次创建生成Session对象。
首先看一下helper的初始化过程。
1 public DevOpenHelper(Context context, String name, CursorFactory factory) { 2 super(context, name, factory); 3 }
调用父类的构造方法
1 /** 2 * SQLiteOpenHelper主要用来创建数据库,该类的构造器中,调用Context中的方法创建并打开一个指定名称的数据库对象。 3 * CursorFactory是创建Cursor的工厂类,参数是为了可以自定义Cursor创建。 4 * 5 * @author zh 6 * 7 */ 8 public static abstract class OpenHelper extends SQLiteOpenHelper { 9 public OpenHelper(Context context, String name, CursorFactory factory) { 10 super(context, name, factory, SCHEMA_VERSION); 11 } 12 // 当数据库被首次创建时执行该方法,一般将创建表等初始化操作在该方法中执行。 13 @Override 14 public void onCreate(SQLiteDatabase db) { 15 Log.i("greenDAO", "Creating tables for schema version " 16 + SCHEMA_VERSION); 17 createAllTables(db, false); 18 } 19 }
在父类中完成数据库的创建。
1 /** Creates underlying database table using DAOs. */ 2 // 通过DAO层创建底层数据库表 3 public static void createAllTables(SQLiteDatabase db, boolean ifNotExists) { 4 // 引用PersonDao中的createTable方法 5 PersonDao.createTable(db, ifNotExists); 6 }
1 public static void createTable(SQLiteDatabase db, boolean ifNotExists) { 2 String constraint = ifNotExists? "IF NOT EXISTS ": ""; 3 db.execSQL("CREATE TABLE " + constraint + "‘PERSON‘ (" + // 4 "‘_id‘ INTEGER PRIMARY KEY ," + // 0: id 5 "‘NAME‘ TEXT NOT NULL ," + // 1: name 6 "‘AGE‘ INTEGER," + // 2: age 7 "‘SEX‘ TEXT," + // 3: sex 8 "‘DATE‘ INTEGER," + // 4: date 9 "‘DEP‘ TEXT);"); // 5: dep 10 }
这样一来表的创建过程就清楚了,接下来是DaoMaster的初始化。
1 public DaoMaster(SQLiteDatabase db) { 2 super(db, SCHEMA_VERSION); 3 // registerDaoClass(java.lang.Class<? extends AbstractDao<?,?>> daoClass)是AbstractDaoMaster中的方法 4 registerDaoClass(PersonDao.class); 5 }
显示调用父类的构造方法,接着registerDaoClass(PersonDao.class);(下面的为官网源代码)
1 public AbstractDaoMaster(SQLiteDatabase db, int schemaVersion) { 2 this.db = db; 3 this.schemaVersion = schemaVersion; 4 daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>(); 5 } 6 7 protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) { 8 // DaoConfig是greenDAO的一个内部类. DaoConfig 为DAOS存储了基本的数据, 并交给AbstractDaoMaster来保存. 这个类用来接受从DAO类中所必需的信息。 9 DaoConfig daoConfig = new DaoConfig(db, daoClass); 10 daoConfigMap.put(daoClass, daoConfig); 11 }
看到,上面一句:newDaoConfig()很关键(下面的为官网源代码)
1 public DaoConfig(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>> daoClass) { 2 this.db = db; 3 try { 4 this.tablename = (String) daoClass.getField("TABLENAME").get(null); 5 Property[] properties = reflectProperties(daoClass); 6 this.properties = properties; 7 allColumns = new String[properties.length]; 8 List<String> pkColumnList = new ArrayList<String>(); 9 List<String> nonPkColumnList = new ArrayList<String>(); 10 Property lastPkProperty = null; 11 for (int i = 0; i < properties.length; i++) { 12 Property property = properties[i]; 13 String name = property.columnName; 14 allColumns[i] = name; 15 if (property.primaryKey) { 16 pkColumnList.add(name); 17 lastPkProperty = property; 18 } else { 19 nonPkColumnList.add(name); 20 } 21 } 22 String[] nonPkColumnsArray = new String[nonPkColumnList.size()]; 23 nonPkColumns = nonPkColumnList.toArray(nonPkColumnsArray); 24 String[] pkColumnsArray = new String[pkColumnList.size()]; 25 pkColumns = pkColumnList.toArray(pkColumnsArray); 26 pkProperty = pkColumns.length == 1 ? lastPkProperty : null; 27 statements = new TableStatements(db, tablename, allColumns, pkColumns); 28 if (pkProperty != null) { 29 Class<?> type = pkProperty.type; 30 keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)|| type.equals(Integer.class) 31 ||type.equals(short.class) || type.equals(Short.class)| type.equals(byte.class) || type.equals(Byte.class); 32 } else { 33 keyIsNumeric = false; 34 } 35 } catch (Exception e) { 36 throw new DaoException("Could not init DAOConfig", e); 37 } 38 }
上面的这个方法就是完成DaoConfig的配置的,通过反射机制,获取我们的Dao类,比如说PersonDao.class,具体的代码可以看上面,就是通过反射。注意statements是TableStatements类型的。
继续,newSession();
1 public DaoSession newSession() { 2 return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); 3 }
下面的代码中 registerDao(Person.class, personDao)
1 public DaoSession(SQLiteDatabase db, IdentityScopeType type, 2 Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) { 3 // 调用父类的数据库 4 super(db); 5 // 将PersonDao中的重要数据复制保存一份并付给personDaoConfig 6 personDaoConfig = daoConfigMap.get(PersonDao.class).clone(); 7 // 判断从PersonDao传递过来的数据的类型范围 8 personDaoConfig.initIdentityScope(type); 9 // 调用PersonDao的父类AbstractDao中的构造方法。 10 personDao = new PersonDao(personDaoConfig, this); 11 //protected <T> void registerDao(java.lang.Class<T> entityClass, AbstractDao<T,?> dao)(官网中只给出了这个,对于含义并未做出解释) 12 registerDao(Person.class, personDao); 13 }
下面这个段代码来源于官网,initIdentityScope()这个函数就是用来判断类型范围的,一般我们不需要管。
1 public void initIdentityScope(IdentityScopeType type) { 2 if (type == IdentityScopeType.None) { 3 identityScope = null; 4 } else if (type == IdentityScopeType.Session) { 5 if (keyIsNumeric) { 6 identityScope = new IdentityScopeLong(); 7 } else { 8 identityScope = new IdentityScopeObject(); 9 } 10 } else { 11 throw new IllegalArgumentException("Unsupported type: " + type); 12 } 13 }
2.2.3 CRUD操作
在我的这个Android项目中,CRUD操作通过Button按钮的点击事件来完成,如图所示:
添加数据:
添加数据直接调用USER中的构造器,直接将从手机屏幕获取到的数据转存进DAO层中,最后提交给数据库。
1 case R.id.addBtn: 2 // 添加数据 3 addDate(); 4 break;
1 private void addDate() { 2 USER user = new USER(Long.valueOf(mId.getText().toString()), 3 mName.getText().toString(), Integer.valueOf(mAge.getText() 4 .toString()), mDep.getText().toString()); 5 Long msg = dao.insert(user); 6 mId.setText(""); 7 mName.setText(""); 8 mAge.setText(""); 9 mDep.setText(""); 10 11 Log.i("MAIN_TAG", "添加成功 personID" + msg); 12 }
官网代码:
删除数据
我所做的删除数据是最简单的删除全部数据,暂时没有按照条件删除等那种复杂的数据删除。
1 case R.id.deleteBtn: 2 list = dao.queryBuilder().list(); 3 for (USER person1 : list) { 4 dao.delete(person1); 5 }
官网代码:
更改数据
1 case R.id.updateBtn: 2 list = dao.queryBuilder().list(); 3 USER user = list.get(0); 4 user.setName("啦啦"); 5 user.setAge(16); 6 7 updateDate(user); 8 break;
1 private void updateDate(USER user) { 2 dao.insertOrReplace(user); 3 4 }
官网代码
查询数据
这里是查询数据库中的所有的数据
1 case R.id.queryBtn: 2 queryDate(); 3 break;
1 private void queryDate() { 2 list = dao.queryBuilder().list(); 3 Log.i("MAIN_TAG", "查询结果" + list); 4 }
官网代码
2.2.4 GreenDAO对数据表的操作
增加表字段
如上图所示,当需要增加字段时,只要在之前java工程中添加想输入的字段的名字即可,然后在重新生成。对于重新生成这里只要你不更改传入的数据库的版本号(如上图示),那么当你增加完新的字段后原来的数据库中的数据依然会被保留,因此不必担心原来的数据存在丢失的风险。
增加新表
同样如图示,如果想在数据库中增加新的表,只需在之前的java工程中按照图中标注的那样即可添加新的表。如果你想让之前的数据和表都存在,那就千万不要更改
Schema schema = new Schema(2, "com.example.greendao");
中的版本号,否则原来的数据和表都将会更新删除。