GreenDao3.0 源码分析-Dao层

GreenDao3.0系列文章:

GreenDao3.0源码分析-Helper

GreenDao3.0 源码分析-DaoMaster和DaoSeesion

GreenDao3.0 源码分析-Dao层

Dao 是GreenDao进行数据查询的一层,起到非常重要的作用,今晚我们就来聊聊GreenDao是如何做增删改查的吧。

Order实体

我们从稍微复杂的Order进行分析,去除自动生成的代码,源实体是:

public class Order {
@Id
private Long id;
private java.util.Date date;
private long customerId;
@ToOne(joinProperty = "customerId")
private Customer customer;
 }

如上图,Order和Customer对象是一对一的关系,下面我们来看看生成的OrderDao:

OrderDao

以下几点是我归纳的要点

1、所有的实体DAO对象都继承自 AbstractDao<Order, Long>。

2、TABLENAME常量定义了数据库表名,默认为实体类名大写,nameInDb 可以自定义表名。

3、每个实体Dao都有一个Properties来管理对象实体属性对表各列的映射关系,对像为Property。

4、提供创建表和删除表的实现。

5、提供Statement绑定对应实例的方法。

6、读取Cursor转化成对象。

7、获得主键。

以上是最基础的操作,是必须的,还有因为一些特殊的:

8、当Java实体需要转成其他类型,比如String存储时,需要提供一个转化器PropertyConverter

9、为了查询告诉,把实体Id设置成RowId

10、还有就是一对多关系,创建的一些关联性代码。

上面归纳的就是实体Dao提供的功能,下面我们逐步对代码进行解析:

1、2、3我就不说了,非常简单,就是通过 Property属性对象来进行管理,每一个Property就是对象数据库的一列。

3、4是数据库的基本操作,通过Database数据库对象执行Sql语句创建表和删除表,每次创建删除是通过DaoMaster进行操作的,两个方法都是静态方法。

我们来简单说下5:

private final Date2LongConver dateConverter = new Date2LongConver();
protected final void bindValues(SQLiteStatement stmt, Order entity) {
    stmt.clearBindings();

    Long id = entity.getId();
    if (id != null) {
        stmt.bindLong(1, id);
    }

    Date date = entity.getDate();
    if (date != null) {
        stmt.bindLong(2, dateConverter.convertToDatabaseValue(date));
    }
    stmt.bindLong(3, entity.getCustomerId());
}</pre>

SQLiteStatement是我们定义好的一些增删改查语句的声明,通过以?来做占位符,达到提供性能的目的,这里就是把Order实例的数据信息,按照序号,进行绑定到SQLiteStatement中,以供数据库做查询等操作,从上面源码我们还能看到,GreenDao的转化器,其实就是按照一定的规则,生成对映的Date2LongConver dateConverter = new Date2LongConver();对象,然后执行方法,达到转换的母目的。

下面我们看看读对象的操作:

public Order readEntity(Cursor cursor, int offset) {
    Order entity = new Order( //
        cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
        cursor.isNull(offset + 1) ? null : dateConverter.convertToEntityProperty(cursor.getLong(offset + 1)), // date
        cursor.getLong(offset + 2) // customerId
    );
    return entity;
}

读的操作也是非常简单,通过判空后进行赋值,相应的需要转化的对象也会转化。

7、8、9各位看官自己查看,比较简单getKey(Order entity)获取主键,updateKeyAfterInsert(Order entity, long rowId)是插入成功后更换Key,hasKey(Order entity)判断是否有主键。

下面我们说说,关于GreenDao是如何实现一对多的问题:

我们再来引入一个类Customer,Customer类和Order是一对多的关系

@ToMany(joinProperties = {
        @JoinProperty(name = "id", referencedName = "customerId")
})
@OrderBy("date ASC")
private List<Order> orders;</pre>

GreenDao处理一对多的关系,是通过取得OrderDao的引用来进行查询:

public List<Order> getOrders() {
    if (orders == null) {
        final DaoSession daoSession = this.daoSession;
        if (daoSession == null) {
            throw new DaoException("Entity is detached from DAO context");
        }
        OrderDao targetDao = daoSession.getOrderDao();
        List<Order> ordersNew = targetDao._queryCustomer_Orders(id);
        synchronized (this) {
            if (orders == null) {
                orders = ordersNew;
            }
        }
    }
    return orders;
}

id就是customerId,我们再看看_queryCustomer_Orders这里方法:

public List<Order> _queryCustomer_Orders(long customerId) {
    synchronized (this) {
        if (customer_OrdersQuery == null) {
            QueryBuilder<Order> queryBuilder = queryBuilder();
            queryBuilder.where(Properties.CustomerId.eq(null));
            queryBuilder.orderRaw("T.'DATE' ASC");
            customer_OrdersQuery = queryBuilder.build();
        }
    }
    Query<Order> query = customer_OrdersQuery.forCurrentThread();
    query.setParameter(0, customerId);
    return query.list();
}</pre>

思路已经很清晰了,就通过获取多对象的Dao引用,进行查询操作,因为这里还添加时间的排序,所以添加增加了时间排序,一对一的关系也大致如此。

Dao已经说完了,接下来我们来进行AbstractDao的解析

AbstractDao

AbstractDao封装了和数据库进行的增删改查功能。

大致功能如下图:

image

AbstractDao提供了插入、更新、删除、保存,查询等功能,额为功能还包括对Rx1.0的适配,统计表的行数等。

因为操作类似,我们这里只分析插入部分,其他部分可通过自己阅读完成理解:

image

可以看到上面的思维导图。子树是面对用户的API,最终单个实体插入会执行到insertInsideTx,而多数据实体会执行到executeInsertInTx。

这里我来理一下思路,GreenDao不管是做查询还是其他操作都是使用Statement来进行优化性能的,而且Statement是可以重用的,所以GreenDao有自己的Statement管理类,就是TableStatements,我们来看看TableStatements对插入声明的创建:

public DatabaseStatement getInsertStatement() {
    if (insertStatement == null) {
        String sql = SqlUtils.createSqlInsert("INSERT INTO ", tablename, allColumns);
        DatabaseStatement newInsertStatement = db.compileStatement(sql);
        synchronized (this) {
            if (insertStatement == null) {
                insertStatement = newInsertStatement;
            }
        }
        if (insertStatement != newInsertStatement) {
            newInsertStatement.close();
        }
    }
    return insertStatement;
}

从上面代码我们知道,TableStatements维护着一个insertStatement对象,如果不为null就直接返回,为null就拼接创建,以达到复用优化性能的作用,这是数据库常见的操作,SqlUtils工具类是Sql语句拼接的工具,大家有兴趣自己看一下。

我们来先聊聊单个实体做插入的时候,从面向用户的API获取到想用的插入,或者插入或替换的Statement后,插入操作会执行

executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach)方法:

private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
    long rowId;
    //先判断当前线程是否连接了数据库
    if (db.isDbLockedByCurrentThread()) {
        //返回true 直接插入数据
        rowId = insertInsideTx(entity, stmt);
    } else {
        //在锁定stmt之前通过开启transation请求连接
        db.beginTransaction();
        try {
            rowId = insertInsideTx(entity, stmt);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
    if (setKeyAndAttach) {
        updateKeyAfterInsertAndAttach(entity, rowId, true);
    }
    return rowId;
}</pre>

到这里 插入步骤如下:

1、通过判断当前线程是否和数据库关联来决定是否需要开启事务。

2、最后都执行insertInsideTx,里面的操作很简单就是调用之前子类的bindValue方法进行绑定值后执行Sql操作。

3、插入后的善后处理,这里就是更新实体的ID为RowId和做内存缓存,还有一些特殊操作实体绑定DaoSeesion,使用active = true会用到。

我再来看看executeInsertInTx,获取到Statement类似,因为是多数据插入,强制使用事务:

这里我们再引入一个概念,就是IdentityScope<K, T>是GreenDao用来做内存缓存的,可以看成是一个Map,如果是Long,GreenDAO做了对应的优化,因为多数据插入是比较耗时的,所以,我们执行插入之前需要加锁,防止多线程的问题。

  SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
                    for (T entity : entities) {
                        bindValues(rawStmt, entity);
                        if (setPrimaryKey) {
                            //执行Sql语句 并返回对象的rowId
                            long rowId = rawStmt.executeInsert();
                            updateKeyAfterInsertAndAttach(entity, rowId, false);
                        } else {
                            rawStmt.execute();
                        }
                    }</pre>

可以看到,多数据插入也只是遍历插入而已。

插入后依然是更新ID,然后就是内存缓存,我们来看看下面这个方法:

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 就是用来维护内存缓存的键值对,通过判断是否加锁执行相应的put操作。

说到这里,GreenDao是怎么优化和做缓存的大家应该都大致了解了吧:

1、通过Statement的复用,达到优化的效果,这是所有数据库都通用的。

2、通过Key映射保存到内存中,保存的值当前是软引用拉,要不很容易爆表。

其他操作类型大家可以花店心思去学一下。

还有就是GreenDao除了用弱引用外,在Key为Long时还特别做了Map的优化,我们将单独抽出来说。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容