[TOC]html
Sqlite数据库是一种轻量级数据库,它具有跨平台,多语言操做等优势,它普遍用于包括浏览器、IOS,Android以及一些便携需求的小型web应用系统。它具有占用资源低,处理速度快等优势。java
SQLiteOpenHelper 是 Android 对于管理 SQLite 数据库建立、打开、关闭、升级、降级以及一些操做回调的帮助类。android
明确: 分析建立和打开数据库是分析打开数据库的链接git
注意: 建立 SQLiteOpenHelper 对象并未进行建立或打开 SQLite 数据库,须要调用 getWritableDatabase 或 getReadableDatabase 方法才可建立或打开数据库。github
//@Link #SQLiteOpenHelper
// 建立或打开一个可用于读、写操做的数据库
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {//同步锁
return getDatabaseLocked(true);
}
}
// 建立或打开一个可用于读、写操做的数据库
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// 数据库关闭须要从新打开
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// 不须要写入数据库,当前数据库也是仅读数据库,则能够返回
return mDatabase;
}
}
...
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
// 若须要写数据库,当前仅可读,须要从新打开数据库
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
// 数据库名字为 null,则再内存中建立一个数据库,数据库关闭,内容消失。
} else if (mName == null) {
db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
} else {
final File filePath = mContext.getDatabasePath(mName);
SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
try {
// 在此进行打开数据库操做
db = SQLiteDatabase.openDatabase(filePath, params);
setFilePermissionsForDb(filePath.getPath());
} catch (SQLException ex) {
if (writable) {
throw ex;
}
//.... 出现异常,则尝试打开可写数据库
params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
db = SQLiteDatabase.openDatabase(filePath, params);
}
}
// 数据库配置完成回调
onConfigure(db);
final int version = db.getVersion();
if (version != mNewVersion) {
//...在此进行对数据库的版本处理
//可能会进行 onBeforeDelete 删除以前、onCreate 建立、onDowngrade 降级、onUpgrade 升级回调
}
// 数据库打开回调
onOpen(db);
...
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}
复制代码
在上面的代码中,数据库名字 null 时经过 SQLiteDatabase.createInMemory(mOpenParamsBuilder.build()); 在内存中建立一个临时数据库,不然经过 SQLiteDatabase.openDatabase(filePath, params); 打开或建立一个指定路径、名字的数据库,下面主要分析打开数据库操做。web
//@Link #SQLiteDatabase
public static SQLiteDatabase openDatabase(@NonNull File path, @NonNull OpenParams openParams) {
return openDatabase(path.getPath(), openParams);
}
private static SQLiteDatabase openDatabase(@NonNull String path, @NonNull OpenParams openParams) {
Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
// SQLiteDatabase 的构造函数中进行数据库的参数配置
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
db.open(); //打开数据库
return db;
}
复制代码
上面的方法都是一些配置信息,最终会调用 db.open() 进行真正的数据库打开操做,进行建立数据库链接池,以及建立初步的数据库链接(SQLiteConnection)。sql
//@Link #SQLiteDatabase
private void open() {
try {
try {
openInner();
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
openInner();
}
} catch (SQLiteException ex) {
Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
close();
throw ex;
}
}
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
// 初始化数据库链接池
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
//...
}
//@Link #SQLiteConnectionPool
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
//...
SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
pool.open();//打开数据库链接池
return pool;
}
private void open() {
// 建立主要数据库链接,经过 SQLiteConnection 能够经过 SQL 语句对数据库进行操做(相似 JDBC)
mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
true /*primaryConnection*/);
synchronized (mLock) {
if (mIdleConnectionHandler != null) {
mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
}
}
//...
}
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration, boolean primaryConnection) {
final int connectionId = mNextConnectionId++;
// 在此打开数据库链接,并返回 SQLiteConnection
return SQLiteConnection.open(this, configuration,
connectionId, primaryConnection); // might throw
}
复制代码
经过上面代码分析,能够知道 SQLiteDatabase 数据库维护一个 SQLiteConnectionPool 数据库链接池,在任什么时候候,数据库链接 SQLiteConnection 都属于数据库链接池。数据库链接池是线程安全的,内部大多数方法都经过 synchronized (mLock) 来实现加锁,可是数据库链接不是线程安全的。数据库
总结一下:SQLiteDatabase 是对数据库链接池和数据库链接的维护以及对操做数据库的方法的封装,数组
SQLiteOpenHelper 是一个用于管理数据库建立、关闭、升级、降级的管理类。最终的数据库操做都是经过 SQLiteConnection 调用数据库 native 层方法来进行。打开数据库至关于建立数据库链接池和建立数据库链接浏览器
明确: 操做数据库是分析如何经过 SQLiteDatabase 获取 SQLiteConnection,并经过数据库链接调用 native 的过程.
SQLiteDatabase 内封装了对数据库操做的方法,能够经过封装的方法,无需进行书写 SQL 语句进行操做数据库,不只如此,SQLiteDatabase 还支持执行原生的 SQL 语句。
SQLiteDatabase 封装操做数据库的方法是经过传入的参数进行拼接成 SQL 语句,再进行操做数据库。选取 Insert 插入操做进行分析,其余操做数据库的方法思想大同小异。
//@Link #SQLiteDatabase
//插入操做 table:表名,nullColumnHack :处理插入空行, values :字段名和值
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder();
// 拼接 SQL 语句的类型
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
// 拼接字段名字
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
// 拼接字段对应的值
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
// 经过 SQL 语句建立 SQLiteStatement 对象
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
// 执行插入操做
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
复制代码
其实在上面已经构建好一个 SQL 语句,以及经过 SQL 语句构建了一个 SQLiteStatement 对象,(SQLiteStatement 是对 SQLiteSession 的一层封,SQLiteSession 用于处理执行 SQL 语句的细节,如:事务、异常等。SQLiteStatement 则是用于调用 SQLiteSession 的方法,及调用方法过程当中出现异常的回调,如:onCorruption())装,那么接下来,须要去挖到底是如何到 SQLConnection 执行这条 SQL 语句。
//@Link #SQLiteStatement
public long executeInsert() {
acquireReference();
try {
// getSession() 返回的是 SQLiteSession 对象,能够理解为相 SQLiteDatabase 控制 SQLiteConnection 的类。
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
//@Link #SQLiteSession
public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal) {
//...在这里向 SQLiteConectionPool 数据库链接池请求获取 SQLiteConnection 数据库链接
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
// 在这里就到了经过 SQLiteConnection 数据库链接进行执行上面构建好了的 SQL 语句
return mConnection.executeForLastInsertedRowId(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
复制代码
在这不进行对在数据库链接池获取 SQLiteConnection 数据库链接的过程进行分析。
总结一下: 在获取到 SQLiteDatabase 后进行数据库操做时,填入的参数首先会进行构建成 SQL 语句,经过 SQL 语句构建成 SQLiteStatement,再获取到 SQLiteSession,经过 SQLiteSession 获取到数据库链接池中的 SQLiteConnection 执行最初构建的 SQL 语句。
SQLiteDatabase 还支持直接执行原生的 SQL 语句(除了 Query 查询操做的 SQL 语句),到 SQLiteStatement 以后的分析与上面的 Insert 插入操做的分析类同,不予再分析。
//@Link #SQLiteDatabase
public void execSQL(String sql) throws SQLException {
executeSql(sql, null);
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {
// DatabaseUtils.getSqlStatementType(sql) 是获取当前 SQL 语句的操做类型
final int statementType = DatabaseUtils.getSqlStatementType(sql);
// DatabaseUtils.STATEMENT_ATTACH 是能够用来 建立或者附加数据库数据库
if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
//... 一些配置操做
}
// 建立 SQLiteStatement 对象
try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
// 经过 executeUpdateDelete 执行 SQL 语句
// 从这能够看出 SQLiteDatabase 不支持经过原生 SQL 进行查询操做。
return statement.executeUpdateDelete();
} finally {
if (statementType == DatabaseUtils.STATEMENT_DDL) {
mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
}
}
} finally {
releaseReference();
}
}
复制代码
Room是一个数据库对象映射库,能够轻松访问Android应用程序上的数据库。Room 提供方便的 API 来查询数据库,并在编译的时候验证这些查询。
Room 有 3 个主要的组件
在编译期间,Room 的注解处理器会自动帮咱们生成访问数据库的类(Dao 类)和生成建立数据库的类(Database 类)。经过 Room 注解处理器生成的类,也至关于普通的写代码,只不过该代码是经过 JDK 自动生成,JDK 搜索全部注解处理器,注解处理器识别注解,反射获取注解做用的类或方法获取到信息参数,再根据参数生成相应的类对应的逻辑。
注解处理器有点像模板功能,咱们输入点内容,而后注解处理器经过咱们输入的内容生成模板代码,避免在开发过程当中反复写这些模板代码,让开发者更加关注业务的逻辑实现。
明确: 框架都是对底层东西进行的封装,对数据库的封装天然是对 SQLiteDatabase 和 SQLiteOpenHelper 的封装,因此先挖一下是在建立 SQLiteDatabase 实例和是哪一个类操做 SQLiteDatabase。
下面是开发过程当中本身实现的 AppDatabase 类,经过该类能够获取到 RoomDatabase 数据库的实例。
//@Link #AppDatabase
@Database(entities = {ArticleEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static final String DB_NAME = "WanAndroid.db";
private static volatile AppDatabase sAppDatabase;
// 一般一个数据库的访问入口在 App 运行期间只有一个实例,因此使用单例模式。
public static synchronized AppDatabase getInstance(Context context) {
if (sAppDatabase == null) {
synchronized (AppDatabase.class) {
if (sAppDatabase == null) {
sAppDatabase = create(context);
}
}
}
return sAppDatabase;
}
private static AppDatabase create(final Context context) {
// 建立 RoomDatabase
return Room.databaseBuilder(
context,
AppDatabase.class,
DB_NAME).build();
}
// 对应上面说的,在被 @Database 做用的类中,必须有一个无参返回 @Dao 做用类对象的抽象方法
public abstract ArticleDao getArticleDao();
}
复制代码
建立 RoomDatabase 实例是经过 Builder 模式建立的,接着来看一下 build() 方法的代码。
//@Link #RoomDatabase.Builder
public T build() {
//...进行一些判断,以及查询线程池和事务线程池(Executor)的建立或初始化
//...
if (mFactory == null) {
// 构建 FrameworkSQLiteOpenHelperFactory 实例
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
DatabaseConfiguration configuration =
new DatabaseConfiguration(
mContext,
mName,
mFactory,
mMigrationContainer,
mCallbacks,
mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mQueryExecutor,
mTransactionExecutor,
mMultiInstanceInvalidation,
mRequireMigration,
mAllowDestructiveMigrationOnDowngrade,
mMigrationsNotRequiredFrom);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);// 经过反射获取到 mDatabaseClass 子类的实例(例子中子类为:AppDatabase_Impl, mDatabaseClass 是 AppDatabase 类)
db.init(configuration); // 配置 mDatabaseClass 子类实例
return db;
}
}
//@Link #FrameworkSQLiteOpenHelperFactory
// FrameworkSQLiteOpenHelperFactory 是用于构建 FrameworkSQLiteOpenHelper 的工厂类
public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
// 全部相关的类对应的对象都要等工厂类调用 create() 方法才被构建。
@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
return new FrameworkSQLiteOpenHelper(
configuration.context, configuration.name, configuration.callback);
}
}
//@Link #FrameworkSQLiteOpenHelper
// FrameworkSQLiteOpenHelper 代理了它的内部类 OpenHelper,OpenHelper(继承了 SQLiteOpenHelper) 维护 FrameworkSQLiteDatabase 数据库的建立以及一些回调
FrameworkSQLiteOpenHelper(Context context, String name, Callback callback) {
mDelegate = createDelegate(context, name, callback);// 建立代理 OpenHelper 实例
}
private OpenHelper createDelegate(Context context, String name, Callback callback) {
// FrameworkSQLiteDatabase 是 SQLiteDatabase 的代理类
// 建立 FrameworkSQLiteDatabase 数组,只是存在引用,未构建实例
final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
return new OpenHelper(context, name, dbRef, callback);
}
//@Link #FrameworkSQLiteOpenHelper.OpenHelper
// OpenHelper 被 FrameworkSQLiteOpenHelper 类代理,是 FrameworkSQLiteOpenHelper 的内部类
OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
final Callback callback) {
super(context, name, null, callback.version,
new DatabaseErrorHandler() {
// 这应该是数据库出错/损坏之类的回调
@Override
public void onCorruption(SQLiteDatabase dbObj) {
// 虽然 getWrappedDb(dbRef, dbObj) 是实例化 FrameworkSQLiteDatabase 的实例
// 可是在此回调未实例化代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 的实例
callback.onCorruption(getWrappedDb(dbRef, dbObj));
}
});
mCallback = callback;
mDbRef = dbRef;
}
//@Link #FrameworkSQLiteOpenHelper.OpenHelper
// 此方法是真正实例化代理了 SQLiteDatabase 的 FrameworkSQLiteDatabase 对象
static FrameworkSQLiteDatabase getWrappedDb(FrameworkSQLiteDatabase[] refHolder, SQLiteDatabase sqLiteDatabase) {
FrameworkSQLiteDatabase dbRef = refHolder[0];
if (dbRef == null || !dbRef.isDelegate(sqLiteDatabase)) {
refHolder[0] = new FrameworkSQLiteDatabase(sqLiteDatabase);
}
return refHolder[0];
}
复制代码
经过上面的分析,能够获得 Room 都是经过代理模式来对原生的 SQLiteOpenHelper 和 SQLiteDatabase 进行封装。上面提到,全部相关的类对应的对象都要等 FrameworkSQLiteOpenHelperFactory 调用 create() 方法才被构建,可是上面并未进行调用 create() 方法,而是将全部的 Room 配置信息都存到了 DatabaseConfiguration 中,那么接着来看 Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); 的方法调用。
//@Link #Room
@SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
//... 获取 klass 类的信息
// 这是获取 klass 对应的子类包名+类名,(如例子中的 klass 类名 = AppDatabase,implName = AppDatabase_Impl)
final String implName = postPackageName.replace('.', '_') + suffix;
try {
@SuppressWarnings("unchecked")
final Class<T> aClass = (Class<T>) Class.forName(
fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
// 在此构建 AppDatabase 的子类 AppDatabase_Impl 的实例
return aClass.newInstance();
}
//... 异常处理
}
复制代码
上面提到 AppDatabase_Impl,这是 @Database 注解对应的注解处理器生成的类(生成路基:{project_root}\app\build\generated\source\apt\debug\{包名}\db\AppDatabase_Impl.java)。在获取到 AppDatabase_Impl 实例后,进行调用 db.init(configuration); 进行配置数据库。接下来要分析配置数据库的过程,不出意外,以前说的 ’FrameworkSQLiteOpenHelperFactory 调用 create() 方法‘ 将会在配置数据库的时候调用。
//@Link #RoomDatabase
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
// 在此建立 SupportSQLiteOpenHelper(FrameworkSQLiteOpenHelper 是具体实现,代理了 OpenHelper)
// createOpenHelper 是抽象方法,具体实如今其子类(例子中为 AppDatabase_Impl)
mOpenHelper = createOpenHelper(configuration);
// 下面都是一些 RoomDatabase 的成员变量的赋值操做
boolean wal = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
mOpenHelper.setWriteAheadLoggingEnabled(wal);
}
mCallbacks = configuration.callbacks;
mQueryExecutor = configuration.queryExecutor;
mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
mWriteAheadLoggingEnabled = wal;
if (configuration.multiInstanceInvalidation) {
mInvalidationTracker.startMultiInstanceInvalidation(configuration.context,
configuration.name);
}
}
//@Link #AppDatabase_Impl
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
//... 建立全部的表
}
//...数据库操做的回调处理
}, "3b4f5822228d6c99c8dc8fa0e6468f7f", "4953443b8f2c213519c76f3b51546987");
final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
.name(configuration.name)
.callback(_openCallback)
.build();
// create() 方法在此调用,进行实例化代理 SQLiteOpenHelper 实例
// Room 到此就算初始化完成
final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
return _helper;
}
复制代码
总结一下: 经过上面的代码能够发现,Room 的实现,是经过代理来进行对原生 SQLiteDatabase 和原生 SQLiteOpenHelper 进行封装,而且经过注解处理器(AnnotationProcessor)进行生成 Database 和 Dao 的代码。
到如今,Room 已经初始化完成,可是还没建立原生 SQLiteDatabase 的实例和代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 实例还没建立,仅存在它的引用,上面提到真正实例化 FrameworkSQLiteDatabase 的方法是 getWrappedDb(FrameworkSQLiteDatabase[] refHolder, SQLiteDatabase sqLiteDatabase)。那么能够初步猜想是在进行数据库操做的时候,会进行调用 getWrappedDb 的方法。
选取经过 Room 插入数据来分析 Room 操做数据库。
被 @Dao 注解做用的接口主要是定义了一些进行操做的方法,@Dao 做用接口配合 @Insert 做用方法的使用,能够进行插入数据进入数据库,经过 RoomDatabase 能够获取 Dao 实例进行操做数据库。
//@Link #ArticleDao
@Dao
public interface ArticleDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(ArticleEntity articleEntities);
}
// 使用
AppDatabase.getInstance(App.getContext()).getArticleDao().insert(entities);
//@Link #AppDatabase_Impl
//getArticleDao() 是获取到 ArticleDao_Impl 的实例(单例模式)
public ArticleDao getArticleDao() {
if (_articleDao != null) {
return _articleDao;
} else {
synchronized(this) {
if(_articleDao == null) {
_articleDao = new ArticleDao_Impl(this);
}
return _articleDao;
}
}
}
复制代码
上面提到 ArticleDao_Impl 类,这个类是咱们写的 ArticleDao 接口的具体实现,固然这个类也是 @Dao 注解的注解处理器进行生成的代码,在获取到 ArticleDao 实例后,能够进行执行 ArticleDao 里面定义的操做数据库的方法。下面来看看经过 ArticleDao 来进行插入操做。
//@Link #ArticleDao_Impl
@Override
public void insert(final ArticleEntity... articleEntities) {
__db.assertNotSuspendingTransaction();// 确认没有暂停的事务
// 开始事务,在这里面会进行 SQLiteDatabase (经过代理类 FrameworkSQLiteOpenHelper.OpenHelper 打开)的打开
__db.beginTransaction();
try {
__insertionAdapterOfArticleEntity.insert(articleEntities);// 插入操做
__db.setTransactionSuccessful();// 设置事务成功
} finally {
__db.endTransaction();// 结束事务
}
}
//@Link #RoomDatabase
@Deprecated
public void beginTransaction() {
assertNotMainThread();// 确认不是在主线程
SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
mInvalidationTracker.syncTriggers(database);
database.beginTransaction();//开始事务
}
//@Link #FrameworkSQLiteOpenHelper
// mDelegate 的引用类型是 FrameworkSQLiteOpenHelper.OpenHelper
@Override
public SupportSQLiteDatabase getWritableDatabase() {
return mDelegate.getWritableSupportDatabase();
}
//@Link #FrameworkSQLiteOpenHelper.OpenHelper
synchronized SupportSQLiteDatabase getWritableSupportDatabase() {
mMigrated = false;
// 在此也进行打开 SQLiteDatabase 数据库的操做(就是原生数据库打开的方式)
SQLiteDatabase db = super.getWritableDatabase();
if (mMigrated) {
close();
return getWritableSupportDatabase();
}
return getWrappedDb(db);
}
FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
// 在这就对应了上面所说的调用 getWrappedDb(mDbRef, sqLiteDatabase) 进行实例化 FrameworkSQLiteDatabase 实例代理 SQLiteDatabase
return getWrappedDb(mDbRef, sqLiteDatabase);
}
复制代码
注意:其中的以 Support 或 Framework 开头的都是 androidx.sqlite 包下的类,也就是说 Room 依赖 androidx 实现
总结一下:在 Room 初始化的时候仅实例化了代理 SQLiteOpenHelper 的 FrameworkSQLiteOpenHelper.OpenHelper,当经过 Room 操做数据库的时候,才会打开 SQLiteDatabse 数据库并将代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 实例化。
接着看经过 Room 进行插入操做 __insertionAdapterOfArticleEntity.insert(articleEntities);。EntityInsertionAdapter 是用于让 Room 知道如何去插入一个实体的类,好比插入一条数据库记录能够经过该实例进行将实体的各个属性进行绑定到 SQL 语句中。
//@Link #EntityInsertionAdapter
public final void insert(T entity) {
final SupportSQLiteStatement stmt = acquire();// 底层经过 RoomDatabase 获取数据库声明(SQLiteStatement)
try {
// 实体和 SQLiteStatement 进行绑定(通俗说就是将实体的内容装到 SQL 语句的占位符中)
bind(stmt, entity);
// 在这就进行执行插入操做了
stmt.executeInsert();
} finally {
release(stmt);
}
}
复制代码
到这就到了上面分析的原生数据库的操做方式了,就不继续向下分析,详情往上看。
经过 @Entity 注解的类对应的是数据库中的一张表,类中的成员变量是表中的各个字段名,该类也是 Room 操做数据库的对象。(注意:必须有 set 和 get 方法)
Room 主要是经过代理来实现封装底层 SQLiteOpenHelper 和 SQLiteDatabase ,以及经过注解处理器的方式来进行生成模板代码,简化开发的代码量,让开发者更快速地操做数据库。
GreenDao 和 Room 差很少,也是将 JAVA 对象映射到 SQLite 数据库中。GreenDao 是一款开源的 Android ORM 数据库框架,具备API 简单,易于使用,支持加密,框架小的特色。
GreenDao 是经过 FreeMarker 来生成模板代码。Room 是经过注解处理器生成模板代码。 FreeMarker 暂未研究。
GreenDao 的使用很简单,只须要经过 @Entity 的注解一个实体类,类对应一张表,类的成员变量对应表中的字段。
//@Link #Note
@Entity(indexes = {
@Index(value = "text, date DESC", unique = true)
})
public class Note {
@Id
private long id;
private String text;
private Date date;
@Generated(hash = 1395965113)
public Note(long id, String text, Date date) {
this.id = id;
this.text = text;
this.date = date;
}
public Note(long id) {
this.id = id;
}
@Generated(hash = 1272611929)
public Note() {
}
//... set get 方法
}
复制代码
经过 @Entity 注解,而后再 Make Project,FreeMarker 就自动帮咱们生成相应的操做数据库的代码,生成代码的路径在{project_root}\app\build\generated\source\greendao\{包名},生成路径里面有 3 个生成类,分别是 DaoMaster(初始化 GreenDao 数据库的类),DaoSession (提供访问 Dao 的方法,会话缓存的类),NoteDao(对数据库进行增删改查的类)。
明确:下面分析这 3 个生成的类是如何封装底层数据库的链接,主要看如何进行初始化底层数据库,以及在操做数据库的过程当中的方法调用栈。
一般咱们在 Application 的 onCreate 里面进行初始化,可是考虑到懒加载提升性能,能够在须要使用 GreenDao 数据库的时候再进行初始化。初始化 GreenDao 的步骤有 4 步,实例化 DevOpenHelper、打开 SQLiteDatabase、实例化 DaoMaster、实例化 DaoSession。
//@Link #App
private void initGreenDao() {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "note_db");
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
sDaoSession = daoMaster.newSession();
}
复制代码
上面的代码包含了 GreenDao 初始化的 4 步,它的方法调用的时序图以下,一步步进行分析
第一步是实例化 DevOpenHelper, DevOpenHelper 继承了 OpenHelper,OpenHelper 继承 了 DatabaseOpenHelper, DatabaseOpenHelper 继承了原生的 SQLiteDatabase,也就是说 GreenDao 初始化第一步就进行了实例化底层的 SQLiteOpenHelper。
//@Link #DaoMaster.OpenHelper
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name) {
super(context, name);
}
//...
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
dropAllTables(db, true);// 删除全部的表
onCreate(db);// 从新建立全部的表
}
}
//@Link #DaoMaster.OpenHelper
// DatabaseOpenHelper 继承了 SQLiteOpenHelper
public static abstract class OpenHelper extends DatabaseOpenHelper {
public OpenHelper(Context context, String name) {
super(context, name, SCHEMA_VERSION);
}
//...
@Override
public void onCreate(Database db) {
createAllTables(db, false);
}
}
复制代码
3 个 Helper 的各自做用
第二步是进行打开数据库的操做,SQLiteDatabase db = helper.getWritableDatabase();,因为 GreenDao 3 个 Helper 类都没有进行重写 getWritableDatabase 方法,全部在此是直接调用原生的 SQLiteOpenHelper 方法,打开数据库,并获取数据库实例。
第三步是实例化 DaoMaster,能够经过 DaoMaster 获取到数据库会话 DaoSession,全部的 Dao 类都要注册到 DaoMaster 里面,DaoMaster 经过 Map 进行存储。
//@Link #DaoMaster
public DaoMaster(SQLiteDatabase db) {
this(new StandardDatabase(db));
}
public DaoMaster(Database db) {
super(db, SCHEMA_VERSION);
registerDaoClass(NoteDao.class);
}
//@Link #AbstractDaoMaster
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
DaoConfig daoConfig = new DaoConfig(db, daoClass);
daoConfigMap.put(daoClass, daoConfig);
}
复制代码
第四步是实例化 DaoSession,在实例化 DaoSession 的过程当中也会进行实例化全部的 Dao 类,并将全部的 Dao 实例进行缓存,用于后面数据库操做时候取用。
//@Link #DaoMaster
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
//@Link #DaoSession
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) {
super(db);
noteDaoConfig = daoConfigMap.get(NoteDao.class).clone();
noteDaoConfig.initIdentityScope(type);
noteDao = new NoteDao(noteDaoConfig, this); // 实例化 NoteDao,可用于操做数据库
registerDao(Note.class, noteDao);
}
//@Link #AbstractDaoSession
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) {
entityToDao.put(entityClass, dao); //缓存 Dao 实例
}
复制代码
总结一下: 到此,GreenDao 数据库就初始化完成,在初始化的过程当中就已经将 SQLiteOpenHelper 实例化和 SQLiteDatabse 进行打开,并且将数据库中的全部的表建立以及处理了数据库升级回调,还实例化全部操做数据库的 Dao 类并将其存到 Map 集合中。
思考:是否能够在须要数据库的时候再进行初始化?是否能够将初始化的 4 步拆开进行优化?
上面提到,DaoSession 里面经过 Map 缓存了全部操做数据库 Dao 的实例,因此在使用的过程当中能够经过 DaoSession 来获取各张表所对应的 Dao ,进行操做数据库中的表。
daoSession.getNoteDao().insert(new Note(0));
//@Link #AbstractDao
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 {
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}
复制代码
GreenDao 操做数据库的方法调用栈比较浅,经过上面代码能够看出,都是经过 GreenDao 的数据库 StandardDatabase(代理原生 SQLiteDatabase 数据库)完成。在这不过多分析。
LitePal是一个开源的Android库,容许开发人员很是容易地使用SQLite数据库。 您无需编写SQL语句便可完成大部分数据库操做,包括建立或升级表,crud操做,聚合函数等.LitePal的设置也很是简单,您能够在不到5个时间内将其集成到项目中。
在进行使用 LitePal 的时候,首先要在 Application 里面进行初始化操做。LitePal.initialize(this);,LitePal 的初始化只是将当前的 Application 的 Context 传入 LitePal 中.
//@Link #LitePal
public static void initialize(Context context) {
Operator.initialize(context);
}
//@Link #Operator
public static void initialize(Context context) {
LitePalApplication.sContext = context;
}
复制代码
在进行经过 LitePal 操做数据库以前,须要调用 LitePal.getDatabase();,进行打开数据库、建立数据库、建立数据库中的表,进行上面的操做天然离不开原生数据库的 SQLiteOpenHelper 类。
LitePal.getDatabase();
//@Link #LitePal
public static SQLiteDatabase getDatabase() {
return Operator.getDatabase();
}
//@Link #Operator
public static SQLiteDatabase getDatabase() {
synchronized (LitePalSupport.class) {
return Connector.getDatabase();
}
}
//@Link #Connector
public static SQLiteDatabase getDatabase() {
return getWritableDatabase();
}
public synchronized static SQLiteDatabase getWritableDatabase() {
LitePalOpenHelper litePalHelper = buildConnection();//LitePalOpenHelper 直接继承 SQLiteOpenHelper
return litePalHelper.getWritableDatabase(); // 进行打开数据库操做
}
复制代码
因为 LitePal 是经过 {project_root}\app\src\main\assets\litepal.xml 这个 XML 文件来进行配置数据库的属性,因此就能够猜出 buildConnection 是进行解析该 XML 文件,并根据 XML 配置的属性进行初始化数据库的操做。接着看 buildConnection() 方法的实现。
private static LitePalOpenHelper buildConnection() {
LitePalAttr litePalAttr = LitePalAttr.getInstance();// 获取 XML 数据库配置文件
litePalAttr.checkSelfValid();
if (mLitePalHelper == null) {
String dbName = litePalAttr.getDbName(); // 解析获取到数据库名字
//... 进行适配数据库建立的位置(内存,外置存储的位置)
// 初始化 LiteOpenHelper
mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion());
}
return mLitePalHelper;
}
复制代码
在经过调用 LitePal 的 getDatabase() 后,数据库被建立打开,其实这里只是一点点封装,封装的内容是经过 XML 文件配置数据库的属性。
要使用 LitePal 进行操做数据库中的表,实体必须继承 LitePalSupport 类。JAVA 的一个类映射到数据库中的一个表,因此 LitePalSupport 类包含操做数据库表的方法(增删查改等)。
public class Music extends LitePalSupport {
@Column(unique = true, defaultValue = "unknown")
private String name;
private float price;
// set get 方法
}
复制代码
经过 LitePal 操做数据库,即经过继承了 LitePalSupport 类的实体进行操做数据库,以插入为例进行分析。
new Music("name" + i, i).save();
//@Link #LitePalSupport
public boolean save() {
try {
saveThrows();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void saveThrows() {
synchronized (LitePalSupport.class) {
SQLiteDatabase db = Connector.getDatabase(); // 获取数据库
db.beginTransaction(); // 开启事务
try {
SaveHandler saveHandler = new SaveHandler(db);
saveHandler.onSave(this); // 在此进行插入操做
clearAssociatedData();
db.setTransactionSuccessful(); // 事务成功
} catch (Exception e) {
throw new LitePalSupportException(e.getMessage(), e);
} finally {
db.endTransaction();// 结束事务
}
}
}
复制代码
SaveHandler 是什么?LitePal 是将对象映射到数据库中,可是底层确定仍是 SQL 语句,因此 SaveHandler 的做用是将对象的成员变量进行赋值到 SQL 语句中(类似的还有 UpdateHandelr、QueryHandler等等)。可是对象的类是 LitePalSupport ,因此还须要经过反射获取子类的属性。
//@Link #SaveHandler
void onSave(LitePalSupport baseObj) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// 获取当前对象的类名
String className = baseObj.getClassName();
// 获取子类成员变量的名字(数据库字段名)
List<Field> supportedFields = getSupportedFields(className);
// 获取通用成员变量的名字(数据库通用字段名)
List<Field> supportedGenericFields = getSupportedGenericFields(className);
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
if (!baseObj.isSaved()) {
analyzeAssociatedModels(baseObj, associationInfos);
doSaveAction(baseObj, supportedFields, supportedGenericFields); // 进行插入操做
analyzeAssociatedModels(baseObj, associationInfos);
} else {
analyzeAssociatedModels(baseObj, associationInfos);
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
}
}
private void doSaveAction(LitePalSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
values.clear();
beforeSave(baseObj, supportedFields, values);
long id = saving(baseObj, values); // 进行插入操做,values 是 ContentValue 的实例
afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
private long saving(LitePalSupport baseObj, ContentValues values) {
if (values.size() == 0) {
values.putNull("id");
}
return mDatabase.insert(baseObj.getTableName(), null, values);// 最终进行插入的操做
}
复制代码
经过上面的方法栈调用,在进行插入操做的时候,是经过 java 反射获取到 module 的各个成员变量的字段名以及相应的值,再进行填充到 ContentValue ,最后经过 SQLiteDatabase 进行插入操做。
主要总结 3 个数据库框架的不一样。
不管是什么框架,由顶向下都是从封装到底层的实现。
打开数据库的方式(在打开数据库的时候会在 SQLiteOpenHelper 的 onCreate 回调中进行建立数据库的表操做)
生成代码/进行封装的方式和内容
最底的封装操做数据库方式
使用/阅读总结:在平常开发中,不建议用原生的 SQLiteOpenHelper 和 SQLiteDatabase 实现,有轮子固然要用要学嘛。在 Room、GreenDao、LitePal 的使用过程,GreenDao 在配置完依赖/插件后是最方便使用了的,可是官方推荐建议使用 Room,仍是最好根据官方的来,可是在学习 Android SQLiteDatabase 的时候,最好仍是读懂一些第三方框架。在本文中没有涉及到各个框架的优化问题(好比:链接池优化、SQLiteStatement 怎么缓存优化等)。