GreenDao3.0 源码分析-Dao层

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010782846/article/details/80295873

GreenDao3.0系列文章:

GreenDao3.0源码分析-Helper

GreenDao3.0 源码分析-DaoMaster和DaoSeesion

GreenDao3.0 源码分析-Dao层

  

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

    Order实体

        我们从稍微复杂的Order进行分析,去除自动生成的代码,源实体是:
        
@Entity(active = true, nameInDb = "ORDERS")
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());
    }
  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;

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

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

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

AbstractDao

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

    大致功能如下图:


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

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


可以看到上面的思维导图。子树是面对用户的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;
    }

到这里 插入步骤如下:

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

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

插入后依然是更新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的优化,我们将单独抽出来说。



猜你喜欢

转载自blog.csdn.net/u010782846/article/details/80295873