GreenDao是一个对象关系映射(ORM)的开源框架,是目前最流行的Android数据库框架。什么是对象关系映射(ORM),就是把对象层次的结构映射成关系结构的过程。因为sqlite是一种关系型的数据库,所以我们要通过SQL语句去操作数据库,这既麻烦也不符合android程序员面向对象的编程习惯,而greendao此时就起了中间桥梁的作用,使我们可以以对象的方式去操作数据库,这其实就是greendao框架在其中把对象的相关参数转化成对应的SQL语句,然后通过SQL语句去操作sqlite而实现的。
然而对于框架的使用我们不能只是浮于表面,下面我们就基于的一个查询的例子展开分析,看看GreenDao里面是怎么的一个实现过程。至于Greendao是配置和使用方法不在本文探讨的范围,下面我们先看我们定义的一个实体类Person:
@Entity
public class Person {
@Id(autoincrement = true)
private Long _id;
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Long get_id() {
return this._id;
}
public void set_id(Long _id) {
this._id = _id;
}
@Generated(hash = 1227494569)
public Person(Long _id, String name) {
this._id = _id;
this.name = name;
}
@Generated(hash = 1024547259)
public Person() {
}
}
这是我们下面要操作的一个类,看看如下代码:
public void queryTest(){
PersonDao personDao= DBHelper.getDaoMaster(context).newSession().getPersonDao();
//可以操作数据库了
//打印sol语句和查询参数values
QueryBuilder.LOG_SQL=true;
QueryBuilder.LOG_VALUES=true;
//插入两个数据
Person person=new Person();
person.setName("王大锤");
Person person2=new Person();
person2.setName("王大锤");
personDao.insert(person);
personDao.insert(person2);
//查询表其中name字段是“王大锤”的数据,根据_id进行排序然后
List<Person> list=personDao.queryBuilder().where(PersonDao.Properties.Name.eq("王大锤")).orderAsc(PersonDao.Properties._id).build().list();
personDao.deleteAll(); //删除全部
Log.d(TAG, "queryTest: 查询返回的数据="+"大小="+list.size()+",id="+list.get(0).get_id()+",名字="+list.get(0).getName()+"/////"+"id="+list.get(1).get_id()+"名字="+list.get(1).getName());
// 1.where 方法:是添加搜索条件,
}
在分析查询动作之前,我们先来看看PersonDao的生成过程是怎样的,因为里面涉及一个很重要的配置过程,我们在创建DaoMaster的时候,会执行一个方法registerDaoClass(PersonDao.class),让我们来看看这个方法里面做了什么
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
DaoConfig daoConfig = new DaoConfig(db, daoClass);
daoConfigMap.put(daoClass, daoConfig);
}
创建一个DaoConfig对象,然后把它保存在HashMap中,来看看DaoConfig的创建过程
public final Database db;
public final String tablename;
public final Property[] properties;
public final String[] allColumns;
public final String[] pkColumns;
public final String[] nonPkColumns;
/** Single property PK or null if there's no PK or a multi property PK. */
public final Property pkProperty;
public final boolean keyIsNumeric;
public final TableStatements statements;
private IdentityScope<?, ?> identityScope;
................................省略
public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) {
this.db = db;
try {
//通过反射得到表的名字
this.tablename = (String) daoClass.getField("TABLENAME").get(null);
//这个方法是获取xxDao里面的静态内部类Properties中的所有Property实例
Property[] properties = reflectProperties(daoClass);
this.properties = properties;
//Person类的所有字段
allColumns = new String[properties.length];
List<String> pkColumnList = new ArrayList<String>();
List<String> nonPkColumnList = new ArrayList<String>();
Property lastPkProperty = null;
for (int i = 0; i < properties.length; i++) {
Property property = properties[i];
String name = property.columnName;
allColumns[i] = name;
if (property.primaryKey) {
pkColumnList.add(name);
lastPkProperty = property;
} else {
nonPkColumnList.add(name);
}
}
String[] nonPkColumnsArray = new String[nonPkColumnList.size()];
nonPkColumns = nonPkColumnList.toArray(nonPkColumnsArray);
String[] pkColumnsArray = new String[pkColumnList.size()];
pkColumns = pkColumnList.toArray(pkColumnsArray);
pkProperty = pkColumns.length == 1 ? lastPkProperty : null;
statements = new TableStatements(db, tablename, allColumns, pkColumns);
if (pkProperty != null) {
Class<?> type = pkProperty.type;
keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)
|| type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class)
|| type.equals(byte.class) || type.equals(Byte.class);
} else {
keyIsNumeric = false;
}
} catch (Exception e) {
throw new DaoException("Could not init DAOConfig", e);
}
}
其实DaoConfig类主要用来保存实体类(比如本例中的Person)字段信息、对应的属性Properties、主键Primary key等等,这些信息都在后面拼接SQL语句的时候会用到。
上面分析DaoConfig的生成过程,这些配置信息暂时用不上,我们接着来看开始的实例代码,我们先通过personDao插入两个名字是"王大锤"的Person对象到数据库表中,然后查询表中Name字段值是“王大锤”的数据结果。下面我们主要基于personDao.queryBuilder().where(PersonDao.Properties.Name.eq("王大锤")).orderAsc(PersonDao.Properties._id).build().list() 这个查询语句展开来分析源码,首先我们来看看queryBuilder方法,代码如下
public QueryBuilder<T> queryBuilder() {
return QueryBuilder.internalCreate(this);
}
public static <T2> QueryBuilder<T2> internalCreate(AbstractDao<T2, ?> dao) {
return new QueryBuilder<T2>(dao);
}
//继续
protected QueryBuilder(AbstractDao<T, ?> dao) {
this(dao, "T");
}
protected QueryBuilder(AbstractDao<T, ?> dao, String tablePrefix) {
this.dao = dao;
this.tablePrefix = tablePrefix;
values = new ArrayList<Object>();
joins = new ArrayList<Join<T, ?>>();
whereCollector = new WhereCollector<T>(dao, tablePrefix);
stringOrderCollation = " COLLATE NOCASE";
}
上面主要创建一个QueryBuilder实例,同时传入PersonDao对象,接下来我们看看where(PersonDao.Properties.Name.eq("王大锤"))方法,而参数PersonDao.Properties.Name.eq("王大锤")返回一个PropertyCondition实例,PropertyCondition继承于WhereCondition,这个类看起来像是用来保存查询条件的,下面我们一起看看源码:
//其中whereCollector里面有一个list集合是用来保存查询条件的
public QueryBuilder<T> where(WhereCondition cond, WhereCondition... condMore) {
whereCollector.add(cond, condMore);
return this;
}
//add方法,添加查询条件
void add(WhereCondition cond, WhereCondition... condMore) {
checkCondition(cond);
whereConditions.add(cond);
for (WhereCondition whereCondition : condMore) {
checkCondition(whereCondition);
whereConditions.add(whereCondition);
}
}
可以看到where方法主要添加保存我们设置的查询条件,下面再看看PersonDao.Properties.Name.eq("王大锤")是怎么样的,如下:
public WhereCondition eq(Object value) {
return new PropertyCondition(this, "=?", value);
}
public PropertyCondition(Property property, String op, Object value) {
super(checkValueForType(property, value));
this.property = property;
this.op = op;
}
PropertyCondition继承于实现WhereCondition接口的AbstractCondition,我们可以看到PropertyCondition会保存一个Property对象和value值,其实Greendao会为每个实体类的属性都生成一个Property对象,比如上面的PersonDao.Properties.Name就是对Person类的Name属性生成的一个Property实例。下面是PersonDao中存在是Property实例:
public static class Properties {
public final static Property _id = new Property(0, Long.class, "_id", true, "_id");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
};
下面来看看orderAsc(PersonDao.Properties._id)方法,同样是传入Properties参数,从方法词义我们可以看出应该是搜索结果按照属性_id升序排列,下面我们来看看代码
public QueryBuilder<T> orderAsc(Property... properties) {
orderAscOrDesc(" ASC", properties);
return this;
}
private void orderAscOrDesc(String ascOrDescWithLeadingSpace, Property... properties)
{
for (Property property : properties) {
checkOrderBuilder();
//拼接order字符串
append(orderBuilder, property);
if (String.class.equals(property.type) && stringOrderCollation != null) {
orderBuilder.append(stringOrderCollation);
}
orderBuilder.append(ascOrDescWithLeadingSpace);
}
}
//append方法
protected StringBuilder append(StringBuilder builder, Property property) {
whereCollector.checkProperty(property);
builder.append(tablePrefix).append('.').append('\'').append(property.columnName).append('\'');
return builder;
}
上面的代码执行是为了给orderBuilder赋值,这个是整个SQL语句中后面的指定排序规格的字符串部分,经过where和orderSrc方法,整个SQL语句中关键的查询条件和查询结果的排序规则这个两部分关键数据已经准备好了,下面就进入build方法看看
public Query<T> build() {
StringBuilder builder = createSelectBuilder();
int limitPosition = checkAddLimit(builder);
int offsetPosition = checkAddOffset(builder);
String sql = builder.toString();
checkLog(sql);
return Query.create(dao, sql, values.toArray(), limitPosition, offsetPosition);
}
上面代码的关键是createSelectBuilder()方法,这个方法基本上创建了完整的查询SQL语句了,我们看看通过log打印出来的sql字符串,sql——>“SELECT T."_id",T."NAME" FROM "PERSON" T WHERE T."NAME"=? ORDER BY T.'_id' ASC”,另外values——>"王大锤"。我们来看看createSelectBuilder方法
private StringBuilder createSelectBuilder() {
String select = SqlUtils.createSqlSelect(dao.getTablename(), tablePrefix, dao.getAllColumns(), distinct);
StringBuilder builder = new StringBuilder(select);
appendJoinsAndWheres(builder, tablePrefix);
if (orderBuilder != null && orderBuilder.length() > 0) {
builder.append(" ORDER BY ").append(orderBuilder);
}
return builder;
}
其中上面的createSqlSelect方法的参数dao.getAllColumns()就用到了前面registerDaoClass(PersonDao.class)方法保存的DaoConfig的相关值,上面也还可以看到排序相关的orderBuilder也会拼接上去,最后生成了一个接近完整的SQL语句字符串的stringbuilder值。然后通过Query的create方法生成一个Query对象,下面我们来看看最后的list()方法
public List<T> list() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadAllAndCloseCursor(cursor);
}
其实dao.getDatabase()返回的是StandardDatabase对象,里面利用SQLiteDatabase封装了一些数据操作的方法,比如rawQuery(sql,parameters)方法如下,其中delegate是我们官方sqlite数据库的SQLiteDatabase实例。
public Cursor rawQuery(String sql, String[] selectionArgs) {
return delegate.rawQuery(sql, selectionArgs);
}
上面的参数selectArgs就是查询条件“王大锤”了,然后就是获得Cursor数据库游标了,通过cursor我们可以就可以获取到对应的数据了。
整个GreenDao层面的数据查询方法的源码分析大概就是这样了,我们可以总结一下,其实就是一开始通过反射设置相关xxDao类的DaoConfig配置,保存后面要操作的xxDao对象的相关值,比如字段、pk、表名字tableName等等。然后我们通过where方法设置查询的条件,然后是orderSrc方法设置查询排序规则相关参数,到这里查询的设置基本完成了,然后就是build()方法生成SQL语句字符串和查询的参数selectArgs,最后通过list()方法,调用sqlite的rawQuery方法返回cursor并且通过loagAllAndCloseCursor方法获取我们要查询的结果。