美文网首页Android 源码分析
Sqlite 源码分析 -- SQLiteDatabase 使用

Sqlite 源码分析 -- SQLiteDatabase 使用

作者: _夜 | 来源:发表于2017-12-07 17:10 被阅读0次

    注意事项:

    1. 插入单条数据不需要开启事务;

    2. beginTransaction() 获取 mLock 锁后不会释放,直到调用 endTransaction(). 所以在此期间如果有另一个线程进行 CRUD 操作,获取不到 mLock 锁,只能等待;

    3. 执行 sql 语句的正确方式:

    db.beginTransaction();
    try {
      ...
      // 注意该语句放在 try 语句块的最后,表明最终的操作成功
      db.setTransactionSuccessful();
    } finally {
      // 注意该语句放在 finally 语句块中,确定进行 roll back 或 commit
      db.endTransaction();
    }
    
    

    一、beginTransaction() 获取 mLock 锁,且不会释放

    1. 开启事务

    /**
     * Begins a transaction in EXCLUSIVE mode.
     * <p>
     * Transactions can be nested.
     * When the outer transaction is ended all of
     * the work done in that transaction and all of the nested transactions will be committed or
     * rolled back. The changes will be rolled back if any transaction is ended without being
     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
     * </p>
     * <p>Here is the standard idiom for transactions:
     *
     * beginTransaction() 的正确使用方式:
     *
     * <pre>
     *   db.beginTransaction();
     *   try {
     *     ...
     *     // 注意该语句放在 try 语句块的最后,表明最终的操作成功
     *     db.setTransactionSuccessful();
     *   } finally {
     *     // 注意该语句放在 finally 语句块中,进行 roll back 或 commit
     *     db.endTransaction();
     *   }
     * </pre>
     */
    public void beginTransaction() {
        beginTransaction(null /* transactionStatusCallback */, true);
    }
    
    

    2. 获取 mLock 锁,然后执行 execSQL("BEGIN EXCLUSIVE;");

    private void beginTransaction(SQLiteTransactionListener transactionListener, boolean exclusive) {
        verifyDbIsOpen();
        // 强制获取 mLock (即使是单线程), 当前线程循环等待获取 mLock 锁,直到获取为止
        // beginTransaction() 执行成功后不会释放该锁, 锁在 endTransaction() 中释放
        lockForced(BEGIN_SQL);
        boolean ok = false;
        try {
            // If this thread already had the lock then get out
            if (mLock.getHoldCount() > 1) {
                if (mInnerTransactionIsSuccessful) {
                    // 在 setTransactionSuccessful() 和 endTransaction() 语句中间不允许调用 beginTransaction()
                    String msg = "Cannot call beginTransaction between " + "calling setTransactionSuccessful and endTransaction";
                    IllegalStateException e = new IllegalStateException(msg);
                    Log.e(TAG, "beginTransaction() failed", e);
                    throw e;
                }
                ok = true;
                // 如果当前线程之前已经持有了 mLock, 则直接返回, (意味着下面的操作已经执行过了,无需再执行)
                return;
            }
            // 该线程第一次持有 mLock (mLock.getHoldCount() == 1)
            // This thread didn't already have the lock, so begin a database transaction now.
            if (exclusive && mConnectionPool == null) {
                // beginTransaction() 走这里
                execSQL("BEGIN EXCLUSIVE;");
            } else {
                execSQL("BEGIN IMMEDIATE;");
            }
            mTransStartTime = SystemClock.uptimeMillis();
            mTransactionListener = transactionListener;
            mTransactionIsSuccessful = true;
            mInnerTransactionIsSuccessful = false;
            if (transactionListener != null) {
                try {
                    transactionListener.onBegin();
                } catch (RuntimeException e) {
                    execSQL("ROLLBACK;");
                    throw e;
                }
            }
            ok = true;
        } finally {
            if (!ok) {
                // beginTransaction is called before the try block so we must release the lock in the case of failure.
                // beginTransaction 成功后不会释放锁, 锁在 endTransaction 中释放
                unlockForced();
            }
        }
    }
    
    

    3. 通过 SQLiteStatement 执行 executeUpdateDelete

    public void execSQL(String sql) throws SQLException {
        executeSql(sql, null);
    }
    
    
    private int executeSql(String sql, Object[] bindArgs) throws SQLException {
        if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
            // "BEGIN ..." 不会跳入该逻辑
            disableWriteAheadLogging();
            mHasAttachedDbs = true;
        }
    
        // 通过 SQLiteStatement 执行 executeUpdateDelete
        SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
        try {
            return statement.executeUpdateDelete();
        } catch (SQLiteDatabaseCorruptException e) {
            onCorruption();
            throw e;
        } finally {
            statement.close();
        }
    }
    

    4. 调用 native 方法 native_executeSql(mSql)

    /**
     * 顾名思义,executeUpdateDelete(), SQLiteDatabase 调用 update() 和 delete() 方法时会走这里
     * 调用 beginTransaction() 也会走这里
     *
     * Execute this SQL statement, if the the number of rows affected by execution of this SQL
     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
     *
     * @return the number of rows affected by this SQL statement execution.
     * @throws android.database.SQLException If the SQL string is invalid for
     *         some reason
     */
    public int executeUpdateDelete() {
        try {
            // 缓存 STATEMENT_UPDATE 和 STATEMENT_BEGIN 类型的 sql 语句
            saveSqlAsLastSqlStatement();
            // 执行每个 sql 语句前都会执行该方法
            // 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
            acquireAndLock(WRITE);
            int numChanges = 0;
            if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
                // beginTransaction() 会走这里
                // since the statement doesn't have to be prepared,
                // call the following native method which will not prepare
                // the query plan
                native_executeSql(mSql);
            } else {
                // update() 和 delete() 会走这里
                numChanges = native_execute();
            }
            return numChanges;
        } finally {
            // beginTransaction() 不会释放 mLock 锁,, 隐式事务会释放锁
            releaseAndUnlock();
        }
    }
    
    

    5. 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁

    /**
     * 执行每个 sql 语句前都会执行该方法
     * 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
     *
     * Called before every method in this class before executing a SQL statement,
     * this method does the following:
     * <ul>
     *   <li>make sure the database is open</li>
     *   <li>get a database connection from the connection pool,if possible</li>
     *   <li>notifies {@link BlockGuard} of read/write</li>
     *   <li>if the SQL statement is an update, start transaction if not already in one.
     *   otherwise, get lock on the database</li>
     *   <li>acquire reference on this object</li>
     *   <li>and then return the current time _after_ the database lock was acquired</li>
     * </ul>
     * <p>
     * This method removes the duplicate code from the other public
     * methods in this class.
     */
    private long acquireAndLock(boolean rwFlag) {
        mState = 0;
        // use pooled database connection handles for SELECT SQL statements
        mDatabase.verifyDbIsOpen();
        // 获取的 db 仍然是 mDatabase
        SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) ? mDatabase.getDbConnection(mSql) : mDatabase;
        // use the database connection obtained above
        mOrigDb = mDatabase;
        mDatabase = db;
        setNativeHandle(mDatabase.mNativeHandle);
        if (rwFlag == WRITE) {
            BlockGuard.getThreadPolicy().onWriteToDisk();
        } else {
            BlockGuard.getThreadPolicy().onReadFromDisk();
        }
    
        /*
         * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
         * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
         * beginTransaction() methods in SQLiteDatabase call lockForced() before
         * calling execSQL("BEGIN transaction").
         */
        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
            if (!mDatabase.isDbLockedByCurrentThread()) {
                // transaction is  NOT started by calling beginTransaction() methods in
                // SQLiteDatabase
                mDatabase.setTransactionUsingExecSqlFlag();
            }
        } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_UPDATE) {
            // got update SQL statement. if there is NO pending transaction, start one
            if (!mDatabase.inTransaction()) {
                mDatabase.beginTransactionNonExclusive();
                mState = TRANS_STARTED;
            }
        }
        // do I have database lock? if not, grab it.
        if (!mDatabase.isDbLockedByCurrentThread()) {
            // 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
            mDatabase.lock(mSql);
            mState = LOCK_ACQUIRED;
        }
    
        acquireReference();
        long startTime = SystemClock.uptimeMillis();
        mDatabase.closePendingStatements();
        compileAndbindAllArgs();
        return startTime;
    }
    
    

    6. beginTransaction() 不会释放 mLock 锁,, 隐式事务会释放锁

    /**
     * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
     */
    private void releaseAndUnlock() {
        releaseReference();
        if (mState == TRANS_STARTED) {
            try {
                mDatabase.setTransactionSuccessful();
            } finally {
                mDatabase.endTransaction();
            }
        } else if (mState == LOCK_ACQUIRED) {
            // beginTransaction() 不会走这里, 隐式事务会走这里, 即 beginTransaction() 不会释放 mLock 锁
            mDatabase.unlock();
        }
        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_COMMIT
                || (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_ABORT) {
            mDatabase.resetTransactionUsingExecSqlFlag();
        }
        clearBindings();
        // release the compiled sql statement so that the caller's SQLiteStatement no longer
        // has a hard reference to a database object that may get deallocated at any point.
        release();
        // restore the database connection handle to the original value
        mDatabase = mOrigDb;
        setNativeHandle(mDatabase.mNativeHandle);
    }
    
    

    二、setTransactionSuccessful() 设置操作成功标识

    /**
     * setTransactionSuccessful() 与 endTransaction() 之间尽量不要做别的事情
     *
     * Marks the current transaction as successful. Do not do any more database work between
     * calling this and calling endTransaction. Do as little non-database work as possible in that
     * situation too. If any errors are encountered between this and endTransaction the transaction
     * will still be committed.
     *
     * @throws IllegalStateException if the current thread is not in a transaction or the
     * transaction is already marked as successful.
     */
    public void setTransactionSuccessful() {
        verifyDbIsOpen();
        if (!mLock.isHeldByCurrentThread()) {
            throw new IllegalStateException("no transaction pending");
        }
        if (mInnerTransactionIsSuccessful) {
            throw new IllegalStateException(
                    "setTransactionSuccessful may only be called once per call to beginTransaction");
        }
        mInnerTransactionIsSuccessful = true;
    }
    
    

    三、endTransaction() 进行 COMMIT 或 ROLLBACK,释放 mLock 锁

    /**
     * End a transaction. See beginTransaction for notes about how to use this and when transactions
     * are committed and rolled back.
     */
    public void endTransaction() {
        // 如 mLock 锁没有被当前线程占据,则抛出异常(从 beginTransaction 开始,mLock 就一直被当前线程占据)
        verifyLockOwner();
        try {
            if (mInnerTransactionIsSuccessful) { // 事务中的操作全部执行成功的情况
                mInnerTransactionIsSuccessful = false;
            } else {
                mTransactionIsSuccessful = false;
            }
            if (mLock.getHoldCount() != 1) {
                return;
            }
            RuntimeException savedException = null;
            if (mTransactionListener != null) {
                try {
                    if (mTransactionIsSuccessful) {
                        mTransactionListener.onCommit();
                    } else {
                        mTransactionListener.onRollback();
                    }
                } catch (RuntimeException e) {
                    savedException = e;
                    mTransactionIsSuccessful = false;
                }
            }
            if (mTransactionIsSuccessful) { // beginTransaction() 中被设置为 true
                // 执行 "COMMIT;"
                execSQL(COMMIT_SQL);
                // if write-ahead logging is used, we have to take care of checkpoint.
                // TODO: should applications be given the flexibility of choosing when to
                // trigger checkpoint?
                // for now, do checkpoint after every COMMIT because that is the fastest
                // way to guarantee that readers will see latest data.
                // but this is the slowest way to run sqlite with in write-ahead logging mode.
                if (this.mConnectionPool != null) {
                    execSQL("PRAGMA wal_checkpoint;");
                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                        Log.i(TAG, "PRAGMA wal_Checkpoint done");
                    }
                }
                // log the transaction time to the Eventlog.
                if (ENABLE_DB_SAMPLE) {
                    logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
                }
            } else {
                try {
                    // 执行 回滚
                    execSQL("ROLLBACK;");
                    if (savedException != null) {
                        throw savedException;
                    }
                } catch (SQLException e) {
                    if (false) {
                        Log.d(TAG, "exception during rollback, maybe the DB previously " + "performed an auto-rollback");
                    }
                }
            }
        } finally {
            mTransactionListener = null;
            // 释放从 beginTransaction() 就一直占据的 mLock 锁
            unlockForced();
            if (false) {
                Log.v(TAG, "unlocked " + Thread.currentThread() + ", holdCount is " + mLock.getHoldCount());
            }
        }
    }
    
    

    相关文章

      网友评论

        本文标题:Sqlite 源码分析 -- SQLiteDatabase 使用

        本文链接:https://www.haomeiwen.com/subject/lghjixtx.html