SQLite
数据库在Android
系统java
层的核心类包括SQLiteDatabase
, SQLiteConnection
, SQLiteConnectionPool
, SQLiteSession
以及查询操作的SQLiteCursor
和CursorWindow
, 关于查询操作的两个类请参考SQLite数据库的cursor在Android系统Java层及JNI层的实现机制一文的说明.
SQLiteDatabase
是提供给开发人员使用的类, 包括了对数据库连接的操作命令, SQL
语句的执行命令,以及一些其他的数据库管理命令等.
SQLiteConnection
代表数据库连接. 该类在java
层和JNI
层都有实现.SQLite数据库文件每次被打开时, 在底层都返回一个sqlite3
对象, SQLiteConnection类对象可以认为是sqlite3对象的封装.
SQLiteConnectionPool
代表数据库连接池, 即用来管理SQLiteConnection对象. 一个SQLiteConnection对象要么存在于该类对象的数据库连接池中,要么被一个SQLiteSession对象所持有.根据数据库连接池的配置, SQLiteConnectionPool可以根据需要动态的创建SQLiteConnection对象.
SQLiteSession
代表数据库会话.SQLiteDatabase对象在执行数据库命令时,是通过数据库会话对象来执行的, 其管理数据库连接的生命周期.数据库会话对象管理的数据库连接是从数据库连接池中分配的, 命令执行完毕后, 将数据库连接归还数据库连接池.
这几个核心类对象间有如下的关系:
- 一个
SQLiteDatabase
对象中存在一个SQLiteConnectionPool
对象成员变量. 是一对一的关系. - 一个
SQLiteSession
对象中存在一个SQLiteConnection
对象成员变量, 是一对一的关系.因为该数据库连接对象是从数据库连接池中分配的, 所以SQLiteSession对象中还包含了一个SQLiteConnectionPool
对象成员变量. - 一个
SQLiteConnectionPool
对象可以包含多个SQLiteConnection
对象:
// Strong references to all available connections.
private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
new ArrayList<SQLiteConnection>();
private SQLiteConnection mAvailablePrimaryConnection;
SQLiteConnectionPool对象中将SQLiteConnectionPool对象区分为两种:mAvailableNonPrimaryConnections
和mAvailablePrimaryConnection
, 这可以在java
层更方便的控制串行访问. 但是这两种java层的数据库连接对象在SQLite底层对应的sqlite3对象是没有任何区别的.
- 一个
SQLiteDatabase
对象中存在一个ThreadLocal
方式保存的SQLiteSession
对象成员变量:
private final ThreadLocal<SQLiteSession> mThreadSession = new ThreadLocal<SQLiteSession>() {
@Override
protected SQLiteSession initialValue() {
return createSession();
}
};
所以, SQLiteDatabase
对象是可以在多线程中使用的, 这时,一个SQLiteDatabase对象可能有多个SQLiteSession
对象.而多线程环境中的SQLiteSession对象都是从SQLiteDatabase对象对应的SQLiteConnectionPool对象中分配数据库连接对象, 所以要求SQLiteConnectionPool
对象是线程安全的.
下面通过几个典型的数据库操作,从源码角度了解这几个核心类间的交互.
open操作
首先看打开数据库连接的操作,SQLiteDatabase
类共过静态方法open打开一个数据库连接(如果数据库文件不存在,需要创建该文件),并返回该类的一个对象.主要执行函数为(SQLiteDatabase.java):
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
db.open();
return db;
}
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
}
}
进入SQLiteConnectionPool调用其open
函数(SQLiteConnectionPool.java).
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
if (configuration == null) {
throw new IllegalArgumentException("configuration must not be null.");
}
// Create the pool.
SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
pool.open(); // might throw
return pool;
}
// Might throw
private void open() {
// Open the primary connection.
// This might throw if the database is corrupt.
mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
true /*primaryConnection*/); // might throw
// Mark the pool as being open for business.
mIsOpen = true;
mCloseGuard.open("close");
}
// Might throw.
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
boolean primaryConnection) {
final int connectionId = mNextConnectionId++;
return SQLiteConnection.open(this, configuration,
connectionId, primaryConnection); // might throw
}
接着进入SQLiteConnection
类,调用其open
函数(SQLiteConnection.java).
// Might throw.
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
boolean primaryConnection) {
final int connectionId = mNextConnectionId++;
return SQLiteConnection.open(this, configuration,
connectionId, primaryConnection); // might throw
}
// Called by SQLiteConnectionPool only.
static SQLiteConnection open(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
SQLiteConnection connection = new SQLiteConnection(pool, configuration,
connectionId, primaryConnection);
try {
connection.open();
return connection;
} catch (SQLiteException ex) {
connection.dispose(false);
throw ex;
}
}
private void open() {
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
mConfiguration.label,
SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
setPageSize();
setForeignKeyModeFromConfiguration();
setWalModeFromConfiguration();
setJournalSizeLimit();
setAutoCheckpointInterval();
setLocaleFromConfiguration();
// Register custom functions.
final int functionCount = mConfiguration.customFunctions.size();
for (int i = 0; i < functionCount; i++) {
SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
nativeRegisterCustomFunction(mConnectionPtr, function);
}
}
所以会调用JNI
层的SQLiteConnection
对象打开SQLite数据库底层的连接,并将其指针赋值给java
层的mConnectionPtr
变量.然后设置数据库连接的一些属性,比如page
大小,外键约束,是否是WAL
模式等等.
所以, SQLiteDatabase的open调用将打开底层SQLite数据库连接,并建立了SQLiteDatabase, SQLiteConnectionPool和SQLiteConnection三者间的联系.
execSQL操作
SQLiteDatabase
类对象调用execSQL
函数执行SQL
语句的主要步骤如下(SQLiteDatabase.java).
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {
if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
boolean disableWal = false;
synchronized (mLock) {
if (!mHasAttachedDbsLocked) {
mHasAttachedDbsLocked = true;
disableWal = true;
}
}
if (disableWal) {
disableWriteAheadLogging();
}
}
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
SQLiteStatement
也是一个核心类,但是该类是一个数据结构类,在SQL
语句执行命令中使用.该类的构造函数中将执行prepare
,对SQL
语句进行编译.其executeUpdateDelete
为(SQLiteStatement.java):
public int executeUpdateDelete() {
acquireReference();
try {
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
protected final SQLiteSession getSession() {
return mDatabase.getThreadSession();
}
函数getSession
返回SQLiteDatabase
对象在当前线程绑定的SQLiteSession
对象(SQLiteSession.java).
public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForChangedRowCount(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
private void acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
private void releaseConnection() {
assert mConnection != null;
assert mConnectionUseCount > 0;
if (--mConnectionUseCount == 0) {
try {
mConnectionPool.releaseConnection(mConnection); // might throw
} finally {
mConnection = null;
}
}
}
可以看到, acquireConnection
函数将从SQLiteConnectionPool
对象中获得一个数据库连接对象.然后在该数据库连接对象上调用executeForChangedRowCount
. 该函数将调用底层的SQLite数据库.SQL命令执行完毕后, SQLiteSession对象调用releaseConnection
函数,将该数据库连接对象归还给数据库连接池.
因此, 在执行SQL操作时, 将获得当前线程绑定的SQLiteSession
对象,然后进行SQL
编译,从SQLiteConnectionPool
中获得一个SQLiteConnection
对象, 最后在该数据库连接对象上执行相应的操作.
尽管SQLiteDatabase
对象可以在多线程场景中使用, 但是有两点需要注意:
- 请检查是否真的需要在多线程场景中使用
SQLiteDatabase
对象, 因为这可能导致代码可读性和可维护性变差. - 在多线程场景中使用
SQLiteDatabase
对象时,虽然不会有安全问题,但是可能存在阻塞. 阻塞的场景有:在java层, 可能无法从SQLiteConnectionPool对象中获得数据库连接对象,因为SQLiteConnectionPool对象中分配的数据库连接对象已经达到配置上限.在SQLite数据库底层, 由于多线程连接中的读写属性导致阻塞.
开发人员可以直接使用SQLiteDatabase
提供的接口操作数据库,也可以使用SQLiteOpenHelper
封装类操作数据库.该封装类依然会使用上面提到的几个核心类来操作数据库.关于SQLiteOpenHelper类有几点需要注意的是:
- 该类的构造函数并不会打开数据库连接. 在调用其
getReadableDatabase
或者getWritableDatabase
时才会打开数据库连接. - 该类在打开数据库连接时, 提供了几个回调函数:
onConfigure
,onCreate
,onDowngrade
,onUpgrade
和onOpen
.相对于直接使用SQLiteDatabase对象而言, 这只是改变了使用方式, 其实并没有任何新的功能. 也可以使用SQLiteDatabase对象直接调用相应的接口来实现这些回调的功能, 比如可以直接调用SQLiteDatabase对象的getVersion
和setVersion
进行升级或者降级的处理逻辑.