Android 中的数据库 SQLite
SQLite 的简介
Sqlite数据库是一种轻量级数据库,它具备跨平台,多语言操作等优点,它广泛用于包括浏览器、IOS,Android以及一些便携需求的小型web应用系统。它具备占用资源低,处理速度快等优点。
Android 中操作 SQLite 的方式
- SQLiteOpenHelper 和 SQLiteDatabase,Android 内部封装的用于管理数据库创建和版本管理的帮助类。
- Room,Google 官方推荐的使用的操作 SQLite 的方式,也是一个 ORM 数据库框架,也是通过注解将对象映射到 SQLite 数据库中。
- GreenDao,一款轻量级的 ORM 数据库框架,可以通过注解将对象映射到 SQLite 数据库中。
- LitePal,通过 XML 文件配置数据库属性,通过 module 的继承实现将对象映射到 SQLite 数据库中,并进行操作数据库。
SQLiteOpenHelper 和 SQLiteDatabase
SQLiteOpenHelper 是 Android 对于管理 SQLite 数据库创建、打开、关闭、升级、降级以及一些操作回调的帮助类。
SQLiteOpenHelper 创建和打开 SQLite 数据库
明确: 分析创建和打开数据库是分析打开数据库的连接
注意: 创建 SQLiteOpenHelper 对象并未进行创建或打开 SQLite 数据库,需要调用 getWritableDatabase 或 getReadableDatabase 方法才可创建或打开数据库。
//@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); 打开或创建一个指定路径、名字的数据库,下面主要分析打开数据库操作。
//@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)。
//@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 操作数据库
明确: 操作数据库是分析如何通过 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
Room是一个数据库对象映射库,可以轻松访问Android应用程序上的数据库。Room 提供方便的 API 来查询数据库,并在编译的时候验证这些查询。
Room 有 3 个主要的组件
- @Database:该注解是作用于一个继承了 RoomDatabase 数据库的抽象类(abstract)类,在运行期间,可以通过 Room.databaseBuilder 或 Room.inMemoryDatabaseBuilder 获取 RoonmDatabase 实例。该数据库类定义了数据库的实体(Entity)表和数据库访问对象(Dao),也是底层连接的主要访问点。
- @Entity:该注解作用于一个实体类,实体类对应与数据库中一个表,实体类的各个成员属性对应于数据库表中的字段。数据库对于每一个用 @Entity 注解了的类都创建一个对应的表进行存储。
- @Dao:该注解作用于一个类或者一个接口,作为访问数据库的对象。访问数据库的对象是 Room 的主要组件,负责定义访问数据库的方法。在被 @Database 作用的类中,必须有一个无参返回 @Dao 作用类对象的抽象方法。在编译期间会通过注解处理器自动生成被 @Dao 作用的类的具体实现。
在编译期间,Room 的注解处理器会自动帮我们生成访问数据库的类(Dao 类)和生成创建数据库的类(Database 类)。通过 Room 注解处理器生成的类,也相当于普通的写代码,只不过该代码是通过 JDK 自动生成,JDK 搜索所有注解处理器,注解处理器识别注解,反射获取注解作用的类或方法获取到信息参数,再根据参数生成相应的类对应的逻辑。
注解处理器有点像模板功能,我们输入点内容,然后注解处理器通过我们输入的内容生成模板代码,避免在开发过程中反复写这些模板代码,让开发者更加关注业务的逻辑实现。
Room 初始化 @Database
明确: 框架都是对底层东西进行的封装,对数据库的封装自然是对 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 操作数据库 @Dao
选取通过 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);
}
}
复制代码
到这就到了上面分析的原生数据库的操作方式了,就不继续向下分析,详情往上看。
Room 操作的实体 @Entity
通过 @Entity 注解的类对应的是数据库中的一张表,类中的成员变量是表中的各个字段名,该类也是 Room 操作数据库的对象。(注意:必须有 set 和 get 方法)
Room 主要是通过代理来实现封装底层 SQLiteOpenHelper 和 SQLiteDatabase ,以及通过注解处理器的方式来进行生成模板代码,简化开发的代码量,让开发者更快速地操作数据库。
GreenDao
GreenDao 和 Room 差不多,也是将 JAVA 对象映射到 SQLite 数据库中。GreenDao 是一款开源的 Android ORM 数据库框架,具有API 简单,易于使用,支持加密,框架小的特点。
GreenDao 是通过 FreeMarker 来生成模板代码。Room 是通过注解处理器生成模板代码。 FreeMarker 暂未研究。
GreenDao 使用
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 个生成的类是如何封装底层数据库的连接,主要看如何进行初始化底层数据库,以及在操作数据库的过程中的方法调用栈。
GreenDao 初始化
通常我们在 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 的各自作用
- 在 DatabaseOpenHelper 里面,对所有返回数据库的方法都包装了一下,返回的是 GreenDao 的数据库类 StandardDatabase;
- 在 OpenHelper 主要是实现在 onCreate 回调中进行创建所有的表操作;
- 在 DevOpenHelper 中主要处理在数据库升级 onUpgrade 回调中进行删除所有的表和重新创建所有的表操作;
第二步是进行打开数据库的操作,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 步拆开进行优化?
GreenDao 操作数据库
上面提到,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
LitePal是一个开源的Android库,允许开发人员非常容易地使用SQLite数据库。 您无需编写SQL语句即可完成大部分数据库操作,包括创建或升级表,crud操作,聚合函数等.LitePal的设置也非常简单,您可以在不到5个时间内将其集成到项目中。
LitePal 初始化
在进行使用 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 的实体
要使用 LitePal 进行操作数据库中的表,实体必须继承 LitePalSupport 类。JAVA 的一个类映射到数据库中的一个表,所以 LitePalSupport 类包含操作数据库表的方法(增删查改等)。
public class Music extends LitePalSupport {
@Column(unique = true, defaultValue = "unknown")
private String name;
private float price;
// set get 方法
}
复制代码
LitePal 操作数据库
通过 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 是通过打开数据库连接池和打开底层数据库连接进行打开数据库的操作。
- Room 是通过继承了 SQLiteOpenHelper 。在进行操作数据库的时候,先通过 SQLiteOpenHelper 的子类进行打开数据库,再操作数据库。(操作数据库的时候自动打开数据库)
- GreenDao 是通过继承 SQLiteOpenHelper。在操作数据库之前,需要通过 helper.getWritableDatabase();(helper 是SQLiteOpenHelper 的子类)进行打开数据库。(操作数据库之前需要主动打开)
- LitePal 是通过继承 SQLiteOpenHelper。在操作数据库之前,需要通过 LitePal.getDatabase(); 进行打开数据库。(操作数据库之前需要主动打开)
生成代码/进行封装的方式和内容
- SQLiteOpenHelper 和 SQLiteDatabase 是直接封装底层的数据库连接,通过将 JAVA 对象的成员属性拼接到 SQL 语句中进行封装操作数据库的方式。(无代码生成)
- Room 是通过注解处理器生成创建/打开数据库代码(Database_Impl)和生成操作数据库代码(Dao_Impl)。
- GreenDao 是通过 FreeMarker 生成模板代码,生成操作数据库表的 Dao 类、管理数据库所有表的 DaoSession 类、和操作数据库的 DaoMaster 类。
- LitePal 是通过 JAVA 反射将继承了 LitePalSupport 的 Module 类成员属性映射到数据库中的表中,并且通过 JAVA 反射将 Module 类的成员变量整合到原生数据库的 ContentValue 进行操作数据库。(无代码生成)
底的封装操作数据库方式
- SQLiteOpenHelper 和 SQLiteDatabase 是封装 SQLiteStatement 进行操作数据库。(封装操作 SQLiteConnection 数据库连接)
- Room 底封装的是 SQLiteStatement 进行操作数据库。(封装操作 SQLiteConnection 数据库连接)
- GreenDao 底封装的是 SQLiteStatement 进行操作数据库。(封装操作 SQLiteConnection 数据库连接)
- LitePal 底层封装的是 SQLiteDatabase 进行操作数据库。(封装原生的 SQLiteDatabase 数据库)
使用/阅读总结:在日常开发中,不建议用原生的 SQLiteOpenHelper 和 SQLiteDatabase 实现,有轮子当然要用要学嘛。在 Room、GreenDao、LitePal 的使用过程,GreenDao 在配置完依赖/插件后是方便使用了的,但是官方推荐建议使用 Room,还是好根据官方的来,但是在学习 Android SQLiteDatabase 的时候,好还是读懂一些第三方框架。在本文中没有涉及到各个框架的优化问题(比如:连接池优化、SQLiteStatement 怎么缓存优化等)。
作者:Me_滔
链接:https://juejin.cn/post/6844903906279948296