在上一篇GreenDao詳解一中有講到,GreenDao
在build
的時候,會自動生成DaoMaster
, DaoSession
,xxDao
三個文件,我們通過上一篇的知識了解到,插入一條數據可以這樣寫:
private void insertUser(User user){
//DaoMaster daoMaster = DbManager.getDaoMaster(mContext);
DaoMaster.DevOpenHelper mDevOpenHelper = new DaoMaster.DevOpenHelper(mContext, DB_NAME);
DaoMaster mDaoMaster = new DaoMaster(mDevOpenHelper.getWritableDb());
UserDao userDao = daoMaster.newSession().getUserDao();
userDao.insert(user);
}
先來理一理他們的調用關系:
DaoMaster
中創建并獲得DaoSession
,而DaoSession
創建和管理xxDao
,而xxDao
則對xxEntity
進行數據的CRUD
操作。為了直觀,這里給出了一個關系圖:
DaoMaster
DaoMaster
是對數據庫進行相關操作的一個封裝類,內部包括了創建表,刪除表的方法,還包括兩個靜態抽象類OpenHelper
和DevOpenHelper
,OpenHelper
繼承于SQLiteOpenHelper
,通過源碼我們看到最終會調用到createAllTabes
方法來創建數據庫中的表;而DevOpenHelper
繼承于OpenHelper
,主要通過onUpgrade
方法用于數據庫的升級,另外,它還包含一個newSession()
方法,在方法內返回一個DaoSession
對象,DaoSession
是連接GreenDao
框架到SQLite
數據庫的橋梁,通過該對象我們可以得到一個與xxx表相關的操作對象xxxDao
。
我們先來看看DaoMaster
的源碼:
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 1;
/** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
UserDao.createTable(db, ifNotExists);
}
/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
UserDao.dropTable(db, ifExists);
}
/**
* WARNING: Drops all table on Upgrade! Use only during development.
* Convenience method using a {@link DevOpenHelper}.
*/
public static DaoSession newDevSession(Context context, String name) {
Database db = new DevOpenHelper(context, name).getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
return daoMaster.newSession();
}
public DaoMaster(SQLiteDatabase db) {
this(new StandardDatabase(db));
}
public DaoMaster(Database db) {
super(db, SCHEMA_VERSION);
registerDaoClass(UserDao.class);
}
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
public DaoSession newSession(IdentityScopeType type) {
return new DaoSession(db, type, daoConfigMap);
}
/**
* Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
*/
public static abstract class OpenHelper extends DatabaseOpenHelper {
public OpenHelper(Context context, String name) {
super(context, name, SCHEMA_VERSION);
}
public OpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory, SCHEMA_VERSION);
}
@Override
public void onCreate(Database 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) {
super(context, name);
}
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
}
在DaoMaster構造函數中,會調用父類AbstractDaoMaster的構造函數和registerDaoClass方法:
public DaoMaster(Database db) {
super(db, SCHEMA_VERSION);
registerDaoClass(UserDao.class);
}
public AbstractDaoMaster(Database 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);
}
上面的代碼中維護了一個daoConfigMap的集合,它是以daoClass為key,DaoConfig為Value的集合。
DaoConfig源碼:
public final class DaoConfig implements Cloneable {
public final Database db;
public final String tablename;
public final Property[] properties;
public final String[] allColumns;
public final String[] pkColumns;
public final String[] nonPkColumns;
/** Single property PK or null if there's no PK or a multi property PK. */
public final Property pkProperty;
public final boolean keyIsNumeric;
public final TableStatements statements;
private IdentityScope<?, ?> identityScope;
public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) {
this.db = db;
try {
this.tablename = (String) daoClass.getField("TABLENAME").get(null);
Property[] properties = reflectProperties(daoClass);
//省略部分代碼
//pkColumns對應表中主鍵的集合,allColumns表中所有字段集合
statements = new TableStatements(db, tablename, allColumns, pkColumns);
//省略部分代碼
} catch (Exception e) {
throw new DaoException("Could not init DAOConfig", e);
}
}
private static Property[] reflectProperties(Class<? extends AbstractDao<?, ?>> daoClass)
throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
Class<?> propertiesClass = Class.forName(daoClass.getName() + "$Properties");
Field[] fields = propertiesClass.getDeclaredFields();
ArrayList<Property> propertyList = new ArrayList<Property>();
final int modifierMask = Modifier.STATIC | Modifier.PUBLIC;
for (Field field : fields) {
// There might be other fields introduced by some tools, just ignore them (see issue #28)
if ((field.getModifiers() & modifierMask) == modifierMask) {
Object fieldValue = field.get(null);
if (fieldValue instanceof Property) {
propertyList.add((Property) fieldValue);
}
}
}
Property[] properties = new Property[propertyList.size()];
for (Property property : propertyList) {
if (properties[property.ordinal] != null) {
throw new DaoException("Duplicate property ordinals");
}
properties[property.ordinal] = property;
}
return properties;
}
// //省略部分代碼
}
DaoConfig主要通過傳入的daoClass參數并通過反射去拿到對應表中的相應信息,并通過TableStatements(db, tablename, allColumns, pkColumns);
創建一個TableStatements對象。而這個TableStatements對象注釋上面有寫是為特定表創建SQL語句的Help類,而該對象會在AbstractDao類中使用,以方便繼承了AstractDao的類使用。
DaoSession
DaoSession用于獲得能夠操作數據庫的XXDao對象,它建立了與SQLite數據庫之間的連接(會話),其中,getUserDao方法用于返回相UserDao的對象,我們在操作數據庫的時候,通過XXDao對象去對數據庫數據進行CRUD操作。
public class DaoSession extends AbstractDaoSession {
private final DaoConfig userDaoConfig;
private final UserDao userDao;
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db);
userDaoConfig = daoConfigMap.get(UserDao.class).clone();
userDaoConfig.initIdentityScope(type);
userDao = new UserDao(userDaoConfig, this);
registerDao(User.class, userDao);
}
public void clear() {
userDaoConfig.clearIdentityScope();
}
public UserDao getUserDao() {
return userDao;
}
}
DaoSession
繼承于AbstractDaoSession
,而AbstractDaoSession
里面實現了很多操作數據庫表的方法,而這些方法最后會調用到AbstractDao
中的相關方法,現在我們來看看AbstractDaoSession
的實現:
public class AbstractDaoSession {
private final Database db;
private final Map<Class<?>, AbstractDao<?, ?>> entityToDao;
private volatile RxTransaction rxTxPlain;
private volatile RxTransaction rxTxIo;
public AbstractDaoSession(Database 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);
}
//省略部分代碼
}
XXDao
XXDao通過GreenDao生成器生成的數據庫操作類,它繼承于AbstractDao,從而可以調用AbstractDao中對數據庫表進行CRUD的方法。
public class UserDao extends AbstractDao<User, Long> {
public static final String TABLENAME = "USER";
/**
* Properties of entity User.<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 Grade = new Property(2, int.class, "grade", false, "GRADE");
public final static Property Age = new Property(3, Integer.class, "age", false, "AGE");
}
public UserDao(DaoConfig config) {
super(config);
}
public UserDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);
}
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"USER\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\"NAME\" TEXT," + // 1: name
"\"GRADE\" INTEGER NOT NULL ," + // 2: grade
"\"AGE\" INTEGER);"); // 3: age
}
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"USER\"";
db.execSQL(sql);
}
@Override
protected final void bindValues(DatabaseStatement stmt, User entity) {
stmt.clearBindings();
Long id = entity.getId();
if (id != null) {
stmt.bindLong(1, id);
}
String name = entity.getName();
if (name != null) {
stmt.bindString(2, name);
}
stmt.bindLong(3, entity.getGrade());
Integer age = entity.getAge();
if (age != null) {
stmt.bindLong(4, age);
}
}
@Override
protected final void bindValues(SQLiteStatement stmt, User entity) {
stmt.clearBindings();
Long id = entity.getId();
if (id != null) {
stmt.bindLong(1, id);
}
String name = entity.getName();
if (name != null) {
stmt.bindString(2, name);
}
stmt.bindLong(3, entity.getGrade());
Integer age = entity.getAge();
if (age != null) {
stmt.bindLong(4, age);
}
}
//省略部分代碼
}
xxDao里面提供了創建數據表的createTable方法,提供了dropTable方法,使用bindValues進行綁定。
現在我們再來看看AbstractDao的代碼:
public abstract class AbstractDao<T, K> {
//省略部分代碼
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement(), true);
}
private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
rowId = insertInsideTx(entity, stmt);
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}
private long insertInsideTx(T entity, DatabaseStatement stmt) {
synchronized (stmt) {
if (isStandardSQLite) {
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
bindValues(rawStmt, entity);
return rawStmt.executeInsert();
} else {
bindValues(stmt, entity);
return stmt.executeInsert();
}
}
}
//省略部分代碼
}
我們看到AbstractDao類中定義了很多操作數據表的方法,在這里我們截取了insert方法,我們看到外部調用的insert方法實際調用的是executeInsert,而這個方法中還會繼續調用insertInsideTx方法,方法內通過isStandardSQLite變量去判斷是調用sqlite中的executeInsert還是greenDao中的executeInsert。走到這里基本就操作數據表中簡單的看了一遍。
GreenDao的對數據表的CRUD操作GreenDao詳解一已經簡單說明了,在這里就不一一重復了。