GreenDao3.0 源码分析-Dao层

2018-05-13  本文已影响0人  孤独的追寻着

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的优化,我们将单独抽出来说。

上一篇下一篇

猜你喜欢

热点阅读