上一篇我们学习了GreenDAO的CRUD基本操作,可以说是非常的方便的,而且GreenDAO的效率和性能远远高于其它两款流行的ORM框架,下面是我从官网找的一副它们三个ORM框架之间的性能测试的直观图:
那么这篇我们就从源码的角度来学习GreenDAO更深层次的工作原理。
GreenDAO的工作原理图解:
从上篇我们知道,如果Android项目中要使用GreenDAO框架,需要先创建一个Java项目用于生成实体类和DAO类,然后在Android项目中使用这些类,在此过程中分别需要对Android项目添加GreenDAO的核心包依赖和对Java项目添加generator包依赖,所以解析GreenDAO的源码需要解析两部分,而这里只解析GreenDAO核心包在Android项目中的工作原理,generator包中的原理很简单,总的来说有四个作用:就是用于生成实体类、DAO类、建立多表之间的关联以及配置实体类的接口和序列化功能(后面的两个功能后续会讲到)。
而在Android项目中用到的最核心的四个类就是:DaoMaster、DaoSession、实体类、实体Dao类。当然还有与CRUD操作密切联系的Property类(Android项目中的核心包和Java项目中的generator包分别都有这个类,不过这两个类作用分别不同,核心包中的主要是针对实体类的属性的,而generator包中的主要是针对表中的字段的)、QueryBuilder类,也至关重要,不过这两个类相信看下源码就懂了,就不多叙述了,而主要讲解这四个核心类。
四个核心类:
这四个核心类的功能体系如下图所示:
DaoMaster
我们知道在使用GreenDAO时候,我们的入口点就是通过DaoMaster的静态内部类DevOpenHelper来创建一个DevOpenHelper对象
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "students-db", null);
而DevOpenHelper对象又是什么呢?我们都知道在使用SQLite的时候,我们必须通过继承SQLiteOpenHelper类并且实现它内部的一些方法来创建数据库,而这里仅仅通过DevOpenHelper类就成功创建了一个文件名为”students-db”的数据表,那么内部又是怎么实现的呢?我们可以看看DaoMaster类的源码(以上一篇生成的为例讲):
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 1;
/** Creates underlying database table using DAOs. */
public static void createAllTables(SQLiteDatabase db, boolean ifNotExists) {
StudentDao.createTable(db, ifNotExists);
}
/** Drops underlying database table using DAOs. */
public static void dropAllTables(SQLiteDatabase db, boolean ifExists) {
StudentDao.dropTable(db, ifExists);
}
public static abstract class OpenHelper extends SQLiteOpenHelper {
public OpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory, SCHEMA_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
createAllTables(db, false);
}
}
/** 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);
}
}
public DaoMaster(SQLiteDatabase db) {
super(db, SCHEMA_VERSION);
registerDaoClass(StudentDao.class);
}
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
public DaoSession newSession(IdentityScopeType type) {
return new DaoSession(db, type, daoConfigMap);
}
}
从DaoMaster中我们可以发现,DaoMaster除了具有创建表和删除表的两个功能外,还有两个内部类,分别为OpenHelper和DevOpenHelper,而DevOpenHelper继承自OpenHelper,而OpenHelper继承自SQLiteOpenHelper,而重写的onCreate()
方法中调用了createAllTables(db,false);
方法来创建数据表,而createAllTables()
方法中是通过调用StudentDao静态方法来创建表的StudentDao.createTable(db, ifNotExists);
我们点进这个方法中去看个究竟:
public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\"NAME\" TEXT NOT NULL ," + // 1: name
"\"AGE\" INTEGER," + // 2: age
"\"IS_MAN\" INTEGER);"); // 3: is_man
}
发现它内部就是通过sql语句来创建表的,只不过GreenDAO帮我们封装好了,而且你会发现删除表其实也一样:
public static void dropTable(SQLiteDatabase db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"STUDENT\"";
db.execSQL(sql);
}
好了,现在我们知道了通过DevOpenHelper是怎么创建表的,而细心的同学会发现在DevOpenHelper类中实现了onUpgrade()
方法,就是更新数据库的方法,它在更新数据表的时候会把以前的数据表删除后再重新创建,所以这个你必须注意,当我们在利用GreenDAO更新数据表的时候,如果你想以前表中的数据保存下来的话,我们必须自己封装一个方法。接下来就是newSession()
方法了,这个当然就是得到DaoSession实例了,关于DaoSession实例,GreenDAO官方建议不要重新创建新的实例,保持一个单例的引用即可。好了,DaoMaster源码看完了,接下来就是看它的父类AbstractDaoMaster
的源码了,它的源码如下:
public abstract class AbstractDaoMaster {
protected final SQLiteDatabase db;
protected final int schemaVersion;
protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;
public AbstractDaoMaster(SQLiteDatabase db, int schemaVersion) {
this.db = db;
this.schemaVersion = schemaVersion;
daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
}
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
DaoConfig daoConfig = new DaoConfig(db, daoClass);
daoConfigMap.put(daoClass, daoConfig);
}
public int getSchemaVersion() {
return schemaVersion;
}
/** Gets the SQLiteDatabase for custom database access. Not needed for greenDAO entities. */
public SQLiteDatabase getDatabase() {
return db;
}
public abstract AbstractDaoSession newSession();
public abstract AbstractDaoSession newSession(IdentityScopeType type);
}
看这个类的代码,其中最让我们受关注的无非就是这一行了
protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;
这里定义了一个Map集合,Key是继承自AbstractDao类的字节码对象,Value则为DaoConfig对象,而往这个Map集合中put数据是通过这个方法registerDaoClass()
:
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
DaoConfig daoConfig = new DaoConfig(db, daoClass);
daoConfigMap.put(daoClass, daoConfig);
}
所以Map的功能现在很清楚了,就是为每一个EntityDao字节码对象建立与之对应的db数据库的映射关系,从而管理所有的EntityDao类。而这个方法在哪里调用了呢?我们回到DaoMaster的源码中,发现在DaoMaster类的构造方法中调用了,并且传入了Student.class
,所以我们在创建DaoMaster对象的时候也同时为EntityDao类和相应的数据库db建立好了关联。
DaoSession
从上面可知DaoSession对象是通过master.newSession();
创建的。我们看看DaoSession源码,发现它也有一个抽象的父类AbstractDaoSession,我们来看看DaoSession的源码:
public class DaoSession extends AbstractDaoSession {
private final DaoConfig studentDaoConfig;
private final StudentDao studentDao;
public DaoSession(SQLiteDatabase db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db);
studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
studentDaoConfig.initIdentityScope(type);
studentDao = new StudentDao(studentDaoConfig, this);
registerDao(Student.class, studentDao);
}
public void clear() {
studentDaoConfig.getIdentityScope().clear();
}
public StudentDao getStudentDao() {
return studentDao;
}
}
就那么几行,其中最主要的一个方法就是通过getStudentDao()
来得到StudentDao实例,而创建一个StudentDao对象正是在DaoSession的构造方法中,其中有这么一行:
studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
这个正是从在DaoMaster创建的Map集合中取出key
为StudentDao.class
的DaoConfig对象,刚刚就说了Map集合中保寸了StudentDao类对应的数据库db的关系映射,而这个DaoConfig对象正是管理了对应的db对象。然后把这个DaoConfig传给StudentDao(studentDaoConfig, this)
,所以这就说明了我们使用StudentDao对象来进行数据库上的CRUD操作而对应的数据库也会变化的原因,这个过程实际上就是在间接操作数据库。
好了,接下来就是看看它的父类了:
这个类比较长,所以我删除了几个方法,这不影响理解的
public class AbstractDaoSession {
private final SQLiteDatabase db;
private final Map<Class<?>, AbstractDao<?, ?>> entityToDao;
public AbstractDaoSession(SQLiteDatabase db) {
this.db = db;
this.entityToDao = new HashMap<Class<?>, AbstractDao<?, ?>>();
}
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) {
entityToDao.put(entityClass, dao);
}
/** Convenient call for {@link AbstractDao#insert(Object)}. */
public <T> long insert(T entity) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
return dao.insert(entity);
}
/** Convenient call for {@link AbstractDao#insertOrReplace(Object)}. */
public <T> long insertOrReplace(T entity) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
return dao.insertOrReplace(entity);
}
/** Convenient call for {@link AbstractDao#refresh(Object)}. */
public <T> void refresh(T entity) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
dao.refresh(entity);
}
/** Convenient call for {@link AbstractDao#update(Object)}. */
public <T> void update(T entity) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
dao.update(entity);
}
//...
/** Convenient call for {@link AbstractDao#delete(Object)}. */
public <T> void delete(T entity) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
dao.delete(entity);
}
/** Convenient call for {@link AbstractDao#deleteAll()}. */
public <T> void deleteAll(Class<T> entityClass) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entityClass);
dao.deleteAll();
}
/** Convenient call for {@link AbstractDao#load(Object)}. */
public <T, K> T load(Class<T> entityClass, K key) {
@SuppressWarnings("unchecked")
AbstractDao<T, K> dao = (AbstractDao<T, K>) getDao(entityClass);
return dao.load(key);
}
/** Gets the SQLiteDatabase for custom database access. Not needed for greenDAO entities. */
public SQLiteDatabase getDatabase() {
return db;
}
/**
* Creates a new {@link AsyncSession} to issue asynchronous entity operations. See {@link AsyncSession} for details.
*/
public AsyncSession startAsyncSession() {
return new AsyncSession(this);
}
}
可以看到它的父类中,大部分方法都是进行CRUD操作的,而事实上我们在进行CRUD操作都是通过StudentDao对象来进行的,实际上这两种做法没有区别,因为它内部本身就是通过dao对象来进行CRUD操作的,大家看看这些方法的返回值就知道了。
到此,我们只看到了DaoSession源码表面上的功能,这些功能就是它管理了指定模式下所有可用的DAO对象,并且提供了getter方法供我们得到这些DAO对象,它还提供了一些CRUD方法。实际上DaoSession和StudentDao在调用CRUD的方法进行CRUD操作时,其中的查询操作就是最特别的,为什么呢?原因是GreenDao在查询这块加了缓存,有趣吧,GreenDao在查询时使用了弱引用WeakReference
,假如第一次查询时候我查询了小明这个Student的数据,那么它将把小明加入一个SparseArray<WeakReference<Q>>
的集合中,下次如果再次查询小明这个学生的时候,将立即会返回这个引用从而不必再查询数据库(前提是GC还没回收这些引用)。
这个缓存的代码是在AbstractQueryData
类中,如下:
SparseArray<WeakReference<Q>> queriesForThreads = new SparseArray<WeakReference<Q>>();
//...
Q forCurrentThread() {
int threadId = Process.myTid();
synchronized (queriesForThreads) {
WeakReference<Q> queryRef = queriesForThreads.get(threadId);
Q query = queryRef != null ? queryRef.get() : null;
if (query == null) {
gc();
query = createQuery();
queriesForThreads.put(threadId, new WeakReference<Q>(query));
} else {
System.arraycopy(initialValues, 0, query.parameters, 0, initialValues.length);
}
return query;
}
}
Entity(实体类)
对于实体类,这没什么可讲的,就是一个Bean,一个实体类对应一张表,实体类里面有对应各个字段的getter和setter方法
EntityDao(实体Dao类)
同样,它也有一个抽象的父类AbstractDao,我们先看看StudentDao类的源码:
public class StudentDao extends AbstractDao<Student, Long> {
public static final String TABLENAME = "STUDENT";
/**
* Properties of entity Student.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
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");
public final static Property Age = new Property(2, Integer.class, "age", false, "AGE");
public final static Property Is_man = new Property(3, Boolean.class, "is_man", false, "IS_MAN");
};
public StudentDao(DaoConfig config) {
super(config);
}
public StudentDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);
}
/** Creates the underlying database table. */
public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\"NAME\" TEXT NOT NULL ," + // 1: name
"\"AGE\" INTEGER," + // 2: age
"\"IS_MAN\" INTEGER);"); // 3: is_man
}
/** Drops the underlying database table. */
public static void dropTable(SQLiteDatabase db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"STUDENT\"";
db.execSQL(sql);
}
/** @inheritdoc */
@Override
protected void bindValues(SQLiteStatement stmt, Student entity) {
stmt.clearBindings();
Long id = entity.getId();
if (id != null) {
stmt.bindLong(1, id);
}
stmt.bindString(2, entity.getName());
Integer age = entity.getAge();
if (age != null) {
stmt.bindLong(3, age);
}
Boolean is_man = entity.getIs_man();
if (is_man != null) {
stmt.bindLong(4, is_man ? 1L: 0L);
}
}
/** @inheritdoc */
@Override
public Long readKey(Cursor cursor, int offset) {
return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0);
}
/** @inheritdoc */
@Override
public Student readEntity(Cursor cursor, int offset) {
Student entity = new Student( //
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.getString(offset + 1), // name
cursor.isNull(offset + 2) ? null : cursor.getInt(offset + 2), // age
cursor.isNull(offset + 3) ? null : cursor.getShort(offset + 3) != 0 // is_man
);
return entity;
}
/** @inheritdoc */
@Override
public void readEntity(Cursor cursor, Student entity, int offset) {
entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
entity.setName(cursor.getString(offset + 1));
entity.setAge(cursor.isNull(offset + 2) ? null : cursor.getInt(offset + 2));
entity.setIs_man(cursor.isNull(offset + 3) ? null : cursor.getShort(offset + 3) != 0);
}
/** @inheritdoc */
@Override
protected Long updateKeyAfterInsert(Student entity, long rowId) {
entity.setId(rowId);
return rowId;
}
/** @inheritdoc */
@Override
public Long getKey(Student entity) {
if(entity != null) {
return entity.getId();
} else {
return null;
}
}
/** @inheritdoc */
@Override
protected boolean isEntityUpdateable() {
return true;
}
}
其中bindValues()
这个方法就是绑定实体的属性名和表中的字段名的,还有比较重要的就是这个静态内部类Properties,该类中分别对每一个实体类的属性都创建了一个Property对象,而我们可以根据Property来得到这个属性对应表中的列名、是否为主键等值,其中还包括了一些方法,比如判断表中某个字段的值是否和value相等:eq(Object value);
Property源码如下:
public class Property {
public final int ordinal;
public final Class<?> type;
public final String name;
public final boolean primaryKey;
public final String columnName;
public Property(int ordinal, Class<?> type, String name, boolean primaryKey, String columnName) {
this.ordinal = ordinal;
this.type = type;
this.name = name;
this.primaryKey = primaryKey;
this.columnName = columnName;
}
/** Creates an "equal ('=')" condition for this property. */
public WhereCondition eq(Object value) {
return new PropertyCondition(this, "=?", value);
}
/** Creates an "not equal ('<>')" condition for this property. */
public WhereCondition notEq(Object value) {
return new PropertyCondition(this, "<>?", value);
}
/** Creates an "LIKE" condition for this property. */
public WhereCondition like(String value) {
return new PropertyCondition(this, " LIKE ?", value);
}
/** Creates an "BETWEEN ... AND ..." condition for this property. */
public WhereCondition between(Object value1, Object value2) {
Object[] values = { value1, value2 };
return new PropertyCondition(this, " BETWEEN ? AND ?", values);
}
/** Creates an "IN (..., ..., ...)" condition for this property. */
public WhereCondition in(Object... inValues) {
StringBuilder condition = new StringBuilder(" IN (");
SqlUtils.appendPlaceholders(condition, inValues.length).append(')');
return new PropertyCondition(this, condition.toString(), inValues);
}
/** Creates an "IN (..., ..., ...)" condition for this property. */
public WhereCondition in(Collection<?> inValues) {
return in(inValues.toArray());
}
/** Creates an "NOT IN (..., ..., ...)" condition for this property. */
public WhereCondition notIn(Object... notInValues) {
StringBuilder condition = new StringBuilder(" NOT IN (");
SqlUtils.appendPlaceholders(condition, notInValues.length).append(')');
return new PropertyCondition(this, condition.toString(), notInValues);
}
/** Creates an "NOT IN (..., ..., ...)" condition for this property. */
public WhereCondition notIn(Collection<?> notInValues) {
return notIn(notInValues.toArray());
}
/** Creates an "greater than ('>')" condition for this property. */
public WhereCondition gt(Object value) {
return new PropertyCondition(this, ">?", value);
}
/** Creates an "less than ('<')" condition for this property. */
public WhereCondition lt(Object value) {
return new PropertyCondition(this, "<?", value);
}
/** Creates an "greater or equal ('>=')" condition for this property. */
public WhereCondition ge(Object value) {
return new PropertyCondition(this, ">=?", value);
}
/** Creates an "less or equal ('<=')" condition for this property. */
public WhereCondition le(Object value) {
return new PropertyCondition(this, "<=?", value);
}
/** Creates an "IS NULL" condition for this property. */
public WhereCondition isNull() {
return new PropertyCondition(this, " IS NULL");
}
/** Creates an "IS NOT NULL" condition for this property. */
public WhereCondition isNotNull() {
return new PropertyCondition(this, " IS NOT NULL");
}
}
而AbstractDao源码中主要是一些CRUD方法和其它的一些方法:
源码太长了,避免影响篇幅,我删了一部分
public abstract class AbstractDao<T, K> {
protected final SQLiteDatabase db;
protected final DaoConfig config;
protected IdentityScope<K, T> identityScope;
protected IdentityScopeLong<T> identityScopeLong;
protected TableStatements statements;
protected final AbstractDaoSession session;
protected final int pkOrdinal;
public AbstractDao(DaoConfig config) {
this(config, null);
}
@SuppressWarnings("unchecked")
public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
this.config = config;
this.session = daoSession;
db = config.db;
identityScope = (IdentityScope<K, T>) config.getIdentityScope();
if (identityScope instanceof IdentityScopeLong) {
identityScopeLong = (IdentityScopeLong<T>) identityScope;
}
statements = config.statements;
pkOrdinal = config.pkProperty != null ? config.pkProperty.ordinal : -1;
}
public AbstractDaoSession getSession() {
return session;
}
TableStatements getStatements() {
return config.statements;
}
public String getTablename() {
return config.tablename;
}
public Property[] getProperties() {
return config.properties;
}
public Property getPkProperty() {
return config.pkProperty;
}
public String[] getAllColumns() {
return config.allColumns;
}
public String[] getPkColumns() {
return config.pkColumns;
}
public String[] getNonPkColumns() {
return config.nonPkColumns;
}
//...
/**
* Deletes the given entities in the database using a transaction.
*
* @param entities
* The entities to delete.
*/
public void deleteInTx(Iterable<T> entities) {
deleteInTxInternal(entities, null);
}
/**
* Deletes the given entities in the database using a transaction.
*
* @param entities
* The entities to delete.
*/
public void deleteInTx(T... entities) {
deleteInTxInternal(Arrays.asList(entities), null);
}
/**
* Deletes all entities with the given keys in the database using a transaction.
*
* @param keys
* Keys of the entities to delete.
*/
public void deleteByKeyInTx(Iterable<K> keys) {
deleteInTxInternal(null, keys);
}
/**
* Deletes all entities with the given keys in the database using a transaction.
*
* @param keys
* Keys of the entities to delete.
*/
public void deleteByKeyInTx(K... keys) {
deleteInTxInternal(null, Arrays.asList(keys));
}
/** Resets all locally changed properties of the entity by reloading the values from the database. */
public void refresh(T entity) {
assertSinglePk();
K key = getKeyVerified(entity);
String sql = statements.getSelectByKey();
String[] keyArray = new String[] { key.toString() };
Cursor cursor = db.rawQuery(sql, keyArray);
try {
boolean available = cursor.moveToFirst();
if (!available) {
throw new DaoException("Entity does not exist in the database anymore: " + entity.getClass()
+ " with key " + key);
} else if (!cursor.isLast()) {
throw new DaoException("Expected unique result, but count was " + cursor.getCount());
}
readEntity(cursor, entity, 0);
attachEntity(key, entity, true);
} finally {
cursor.close();
}
}
public void update(T entity) {
assertSinglePk();
SQLiteStatement stmt = statements.getUpdateStatement();
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
updateInsideSynchronized(entity, stmt, true);
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
updateInsideSynchronized(entity, stmt, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
}
public QueryBuilder<T> queryBuilder() {
return QueryBuilder.internalCreate(this);
}
}
好了,核心类已经介绍完了,下面梳理一下它们主要做了什么事。
GreenDAO工作原理
我们首先来看看使用GreenDAO的基本步骤:
//生成数据库文件,名为students-db
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "students-db", null);
SQLiteDatabase db = helper.getWritableDatabase();
//建立特定模式下的所有的DAO对象和数据库db对象的映射
DaoMaster master = new DaoMaster(db);
//管理特定模式下的所有DAO对象,并提供一些通用的CRUD持久化方法
DaoSession session = master.newSession();
//得到指定的StudentDao对象
StudentDao dao = session.getStudentDao();
dao.insert(student);
//...