天天看點

GreenDao3.0 源碼分析-Dao層    Order實體    OrderDaoAbstractDao

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封裝了和資料庫進行的增删改查功能。

    大緻功能如下圖:

GreenDao3.0 源碼分析-Dao層    Order實體    OrderDaoAbstractDao

AbstractDao提供了插入、更新、删除、儲存,查詢等功能,額為功能還包括對Rx1.0的适配,統計表的行數等。

因為操作類似,我們這裡隻分析插入部分,其他部分可通過自己閱讀完成了解:

GreenDao3.0 源碼分析-Dao層    Order實體    OrderDaoAbstractDao

可以看到上面的思維導圖。子樹是面對使用者的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的優化,我們将單獨抽出來說。

繼續閱讀