拆轮子系列之理解GreenDao框架源码

2022-09-08 15:46:16 浏览数 (1)

GreenDao概述

如果Android项目中要使用GreenDAO框架,需要先创建一个Java项目用于生成实体类和DAO类,然后在Android项目中使用这些类,在此过程中分别需要对Android项目添加GreenDAO的核心包依赖和对Java项目添加generator包依赖,所以解析GreenDAO的源码需要解析两部分,而这里只解析GreenDAO核心包在Android项目中的工作原理,generator包中的原理很简单,总的来说有四个作用:就是用于生成实体类、DAO类、建立多表之间的关联以及配置实体类的接口和序列化功能

在Android项目中用到的最核心的四个类就是:DaoMaster、DaoSession、实体类、实体Dao类。 这四个核心类的功能体系如下图所示:

DaoMaster

GreenDao框架管理类,该类对数据库相关管理操作进行封装 我们知道在使用GreenDAO时候,我们的入口点就是通过DaoMaster的静态内部类DevOpenHelper来创建一个DevOpenHelper对象

代码语言:javascript复制
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "students-db", null);

而DevOpenHelper对象又是什么呢?我们都知道在使用SQLite的时候,我们必须通过继承SQLiteOpenHelper类并且实现它内部的一些方法来创建数据库,而这里仅仅通过DevOpenHelper类就成功创建了一个文件名为”students-db”的数据表,那么内部又是怎么实现的呢?我们可以看看DaoMaster类的源码:

代码语言:javascript复制
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,OpenHelper继承于SQLiteOpenHelper用于创建所有的数据库表;DevOpenHelper继承于OpenHelper用于数据库升级,而重写的onCreate()方法中调用了createAllTables(db,false);方法来创建数据表,而createAllTables()方法中是通过调用StudentDao静态方法来创建表的StudentDao.createTable(db, ifNotExists);我们点进这个方法中去看个究竟

代码语言:javascript复制
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帮我们封装好了,而且你会发现删除表其实也一样:

代码语言:javascript复制
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的源码了,它的源码如下:

代码语言:javascript复制
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);
}

看这个类的代码,其中最让我们受关注的无非就是这一行了

代码语言:javascript复制
protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;

这里定义了一个Map集合,Key是继承自AbstractDao类的字节码对象,Value则为DaoConfig对象。每个AbstractDao对应着一个DaoConfig,然后保存在Map< Class

代码语言:javascript复制
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对象是连接GreenDao框架到SQLite数据库的纽带,通过该对象我们可以得到一个与数据库某个表相关的操作对象xxxDao。我们看看DaoSession源码,发现它也有一个抽象的父类AbstractDaoSession,我们来看看DaoSession的源码:

代码语言:javascript复制
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的构造方法中,其中有这么一行:

代码语言:javascript复制
studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();

这个正是从在DaoMaster创建的Map集合中取出keyStudentDao.class的DaoConfig对象,刚刚就说了Map集合中保寸了StudentDao类对应的数据库db的关系映射,而这个DaoConfig对象正是管理了对应的db对象。然后把这个DaoConfig传给StudentDao(studentDaoConfig, this),所以这就说明了我们使用StudentDao对象来进行数据库上的CRUD操作而对应的数据库也会变化的原因,这个过程实际上就是在间接操作数据库。

好了,接下来就是看看它的父类了: 这个类比较长,所以我删除了几个方法,这不影响理解的

代码语言:javascript复制
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类中,如下:

代码语言:javascript复制
    final Map<Long, WeakReference<Q>> queriesForThreads;
    //......
    Q forCurrentThread(Q query) {
        if (Thread.currentThread() == query.ownerThread) {
            System.arraycopy(initialValues, 0, query.parameters, 0, initialValues.length);
            return query;
        } else {
            return forCurrentThread();
        }
    }
    Q forCurrentThread() {
        long threadId = Thread.currentThread().getId();
        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,封装了所有对数据库表进行增删改成的方法。可以这么说,我们之所以使用GreenDao管理本地数据库无需与SQL语句打交道,就是因为GreenDao框架在这一层已经对大部分数据库操作SQL语句进行了封装。

同样,它也有一个抽象的父类AbstractDao,我们先看看StudentDao类的源码:

代码语言:javascript复制
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);
    }

    @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);
        }
    }

    @Override
    public Long readKey(Cursor cursor, int offset) {
        return cursor.isNull(offset   0) ? null : cursor.getLong(offset   0);
    }    

    @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;
    }

    @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);
     }

    @Override
    protected Long updateKeyAfterInsert(Student entity, long rowId) {
        entity.setId(rowId);
        return rowId;
    }

    @Override
    public Long getKey(Student entity) {
        if(entity != null) {
            return entity.getId();
        } else {
            return null;
        }
    }

    @Override    
    protected boolean isEntityUpdateable() {
        return true;
    }
}

其中bindValues()这个方法就是绑定实体的属性名和表中的字段名的,还有比较重要的就是这个静态内部类Properties,该类中分别对每一个实体类的属性都创建了一个Property对象,而我们可以根据Property来得到这个属性对应表中的列名、是否为主键等值,其中还包括了一些方法,比如判断表中某个字段的值是否和value相等:eq(Object value); Property源码如下:

代码语言:javascript复制
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方法和其它的一些方法,源码太长了,避免影响篇幅,我删了一部分:

代码语言:javascript复制
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);
    }
}    

好了,核心类已经介绍完了

缓存操作

上面在讲到DaoConfig类的作用的时候,有说到它有获取该表对应的缓存处理实例的作用,那么,它是如何缓存的过程,在DaoSession的构造方法中就有为每个Dao初始化缓存的操作了:

代码语言:javascript复制
    private final DaoConfig accountDaoConfig;

    public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
            daoConfigMap) {
        super(db);

        accountDaoConfig = daoConfigMap.get(AccountDao.class).clone();
        accountDaoConfig.initIdentityScope(type);
        .....
    }

下面来看看DaoConfig类关于缓存的一些代码:

代码语言:javascript复制
    private IdentityScope<?, ?> identityScope;
    .....
    public void initIdentityScope(IdentityScopeType type) {
        if (type == IdentityScopeType.None) {
            identityScope = null;
        } else if (type == IdentityScopeType.Session) {
            if (keyIsNumeric) {
                identityScope = new IdentityScopeLong();
            } else {
                identityScope = new IdentityScopeObject();
            }
        } else {
            throw new IllegalArgumentException("Unsupported type: "   type);
        }
    }

可见它是由IdentityScope接口来实现的,它有两个实现类:IdentityScopeLong类和IdentityScopeObject类,下面看看IdentityScopeObject类:

代码语言:javascript复制
public class IdentityScopeObject<K, T> implements IdentityScope<K, T> {
    private final HashMap<K, Reference<T>> map;
    private final ReentrantLock lock;

    public IdentityScopeObject() {
        map = new HashMap<K, Reference<T>>();
        lock = new ReentrantLock();
    }
    ......
}

在执行插入操作时,会进行缓存,我们从AbstractDao类中代码可知:

代码语言:javascript复制
private long executeInsert(T entity, SQLiteStatement stmt) {
        long rowId;
        if (db.isDbLockedByCurrentThread()) {// database被当前线程给锁住了
            synchronized (stmt) {
                bindValues(stmt, entity);
                rowId = stmt.executeInsert();
            }
        } else {
            // Do TX to acquire a connection before locking the stmt to avoid deadlocks
            db.beginTransaction();
            try {
                synchronized (stmt) {
                    bindValues(stmt, entity);
                    rowId = stmt.executeInsert();
                }
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        }
        updateKeyAfterInsertAndAttach(entity, rowId, true);
        return rowId;
    }

给操作的bean更新字段_id的值:

代码语言:javascript复制
protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock) {
        if (rowId != -1) {
            K key = updateKeyAfterInsert(entity, rowId);
            attachEntity(key, entity, lock);
        } else {
            // TODO When does this actually happen? Should we throw instead?
            DaoLog.w("Could not insert row (executeInsert returned -1)");
        }
    }

缓存操作:IdentityScope键值对,key为表中主键,value为bean

代码语言:javascript复制
protected final void attachEntity(K key, T entity, boolean lock) {
        attachEntity(entity);
        if (identityScope != null && key != null) {
            if (lock) {
                identityScope.put(key, entity);
            } else {
                identityScope.putNoLock(key, entity);
            }
        }
    }

可能会问,identityScope从哪来的,下面看:

代码语言:javascript复制
public abstract class AbstractDao<T, K> {
    protected final IdentityScope<K, T> identityScope;
    protected final IdentityScopeLong<T> identityScopeLong;
    ......

    public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
        this.config = config;
        this.session = daoSession;
        db = config.db;
        isStandardSQLite = db.getRawDatabase() instanceof SQLiteDatabase;
        identityScope = (IdentityScope<K, T>) config.getIdentityScope();
        if (identityScope instanceof IdentityScopeLong) {
            identityScopeLong = (IdentityScopeLong<T>) identityScope;
        } else {
            identityScopeLong = null;
        }
        statements = config.statements;
        pkOrdinal = config.pkProperty != null ? config.pkProperty.ordinal : -1;
    }
}

当query操作时:

代码语言:javascript复制
public T load(K key) {
        assertSinglePk();
        if (key == null) {
            return null;
        }
        if (identityScope != null) {// 如果有缓存,直接从缓存中取值
            T entity = identityScope.get(key);
            if (entity != null) {
                return entity;
            }
        }
        String sql = statements.getSelectByKey();
        String[] keyArray = new String[] { key.toString() };
        Cursor cursor = db.rawQuery(sql, keyArray);
        return loadUniqueAndCloseCursor(cursor);
    }

总结

我们来看看使用GreenDAO的基本步骤:

代码语言:javascript复制
//生成数据库文件,名为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);
//...

GreenDao的逻辑: 1. 实例化一个SQLiteOpenHelper对象,以便建立与指定数据库(如”testDb”)之间的连接; 2. 调用SQLiteOpenHelper的getWritableDatabase()方法以读写方式打开所连接的数据库; 3. 通过获得的数据库对象SQLiteDatabase来创建GreenDao框架管理者DaoMaster对象; 4. 调用DaoMaster的newSession()方法实例化一个数据库会话对象DaoSession; 5. 通过DaoSession对象获得最终能够操作数据库表的xxxxDao对象”

GreenDao优势

模板代码生成

GreenDao官方为什么说自己的数据库框架运行快呢,首先,第一点这个框架不像其他框架通过运行期反射创建ORM映射,而是通过freemarker模板方式在编译期之前帮你生成可用的和数据库生成有关的表帮助对象,所以说这第一点就比其他数据库框架的速度快。

模板就是把共性(固定不变的)的东西提取出来反复使用,节约时间 提高开发效率。现在主流的模板技术包括:FreeMarker和Velocity,模板技术推崇一种模式:输出=模板 数据。 FreeMarker最开始被MVC Web框架用来生成HTML页面,但它的用途不仅限于HTML或者Web领域,比如本文所要介绍的生成JavaBean源代码。

与其他框架的比较

ormlite

基于注解和反射的的方式,导致ormlite性能有着一定的损失(注解其实也是利用了反射的原理)

优点: 文档较全面,社区活跃,有好的维护,使用简单,易上手。

缺点: 基于反射,效率较低

GreenDao

优点: 效率很高,插入和更新的速度是ormlite的2倍,加载实体的速度是ormlite的4.5倍。 文件较小(<100K),占用更少的内存 ,但是需要create Dao, 操作实体灵活:支持get,update,delete等操作

缺点: 学习成本较高。其中使用了一个java工程根据一些属性和规则去generate一些基础代码,类似于javaBean但会有一些规则,另外还有QueryBuilder、Dao等API,所以首先要明白整个过程,才能方便使用。没有ORMLite那样封装的完整,不过greenDao的官网上也提到了这一点,正是基于generator而不是反射,才使得其效率高的多。

参考博文链接: http://blog.csdn.net/u010687392/article/details/48465315 http://blog.csdn.net/andrexpert/article/details/53539417 http://www.jianshu.com/p/09ad8040c996 http://blog.csdn.net/xushuaic/article/details/24434881

0 人点赞