Sqlite 源码分析 -- SQLiteDatabase 使用事务 (API 24)

注意事项:

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

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

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

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

一、beginTransaction() 从连接池获取主连接,且不会释放

1. 开启事务

public void beginTransaction() {
    beginTransaction(null /* transactionStatusCallback */, true);
}

2. 获取当前线程的 SQLiteSession,调用其 beginTransaction()

/**
 * 1. 采用 TRANSACTION_MODE_EXCLUSIVE,只允许当前 session 对数据库进行读写,别的 session 不允许访问数据库
 * 2. 获取当前线程的 SQLiteSession,如果不存在则创建
 * 3. 要求获取主连接
 * @param transactionListener
 * @param exclusive
 */
private void beginTransaction(SQLiteTransactionListener transactionListener, boolean exclusive) {
    // 增加引用次数
    acquireReference();
    try {
        getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE : SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
                transactionListener,
                getThreadDefaultConnectionFlags(false /*readOnly*/), null);
    } finally {
        // 减少引用次数
        releaseReference();
    }
}

3. 从连接池获取主连接,且持有连接不释放

/**
 * 1. 从连接池获取主连接
 * 2. 获取主连接后不会释放, 在 endTransaction() 中释放主连接
 * @param transactionMode
 * @param transactionListener
 * @param connectionFlags
 * @param cancellationSignal
 */
private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, CancellationSignal cancellationSignal) {
    if (cancellationSignal != null) {
        // cancellationSignal 默认为 null
        cancellationSignal.throwIfCanceled();
    }

    if (mTransactionStack == null) {
        // 第一次调用 beginTransaction 的情况,不存在嵌套
        /**
         * 从连接池获取一条连接(此处要求获取主连接),赋值给 mConnection:
         * 1. 多个线程共用一个连接池,获取连接的过程是同步的
         * 2. 若主连接已被占用,则进入循环等待状态,等待主连接被释放
         */
        acquireConnection(null, connectionFlags, cancellationSignal); // might throw
    }
    try {
        // Set up the transaction such that we can back out safely
        // in case we fail part way.
        if (mTransactionStack == null) {
            // Execute SQL might throw a runtime exception.
            switch (transactionMode) {
                case TRANSACTION_MODE_IMMEDIATE:
                    mConnection.execute("BEGIN IMMEDIATE;", null, cancellationSignal); // might throw
                    break;
                case TRANSACTION_MODE_EXCLUSIVE:
                    // 通过主连接调用 native 方法执行 sql
                    mConnection.execute("BEGIN EXCLUSIVE;", null, cancellationSignal); // might throw
                    break;
                default:
                    mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
                    break;
            }
        }

        // Listener might throw a runtime exception.
        if (transactionListener != null) {
            try {
                transactionListener.onBegin(); // might throw
            } catch (RuntimeException ex) {
                if (mTransactionStack == null) {
                    mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
                }
                throw ex;
            }
        }

        // Bookkeeping can't throw, except an OOM, which is just too bad...
        SQLiteSession.Transaction transaction = obtainTransaction(transactionMode, transactionListener);
        // mTransactionStack 若为 null,则 mParent 为 null
        // mTransactionStack 若不为 null,则设为新开的 transaction 的 parent
        transaction.mParent = mTransactionStack;
        // 替换 mTransactionStack 为新开的 transaction,意味着进入子 transaction 环境
        // 后续操作都是针对新的子 transaction
        mTransactionStack = transaction;
    } finally {
        if (mTransactionStack == null) {
            // 正常情况下 mTransactionStack 不会为 null
            // beginTransaction 获取主连接后不会释放
            releaseConnection(); // might throw
        }
    }
}

二、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() {
    acquireReference();
    try {
        // 获取 SQLiteSession,调用其 setTransactionSuccessful()
        getThreadSession().setTransactionSuccessful();
    } finally {
        releaseReference();
    }
}

/**
 * Marks the current transaction as having completed successfully.
 * <p>
 * This method can be called at most once between {@link #beginTransaction} and
 * {@link #endTransaction} to indicate that the changes made by the transaction should be
 * committed.  If this method is not called, the changes will be rolled back
 * when the transaction is ended.
 * </p>
 *
 * @throws IllegalStateException if there is no current transaction, or if
 * {@link #setTransactionSuccessful} has already been called for the current transaction.
 *
 * @see #beginTransaction
 * @see #endTransaction
 */
public void setTransactionSuccessful() {
    throwIfNoTransaction();
    throwIfTransactionMarkedSuccessful();
    // 标记当前的 mTransactionStack
    mTransactionStack.mMarkedSuccessful = true;
}

三、endTransaction()

1. 调用 SQLiteDatabase 的 endTransaction()

public void endTransaction() {
    acquireReference();
    try {
        getThreadSession().endTransaction(null);
    } finally {
        releaseReference();
    }
}

2. 调用 SQLiteSession 的 endTransaction()

/**
 * Ends the current transaction and commits or rolls back changes.
 * <p>
 * If this is the outermost transaction (not nested within any other
 * transaction), then the changes are committed if {@link #setTransactionSuccessful}
 * was called or rolled back otherwise.
 * </p><p>
 * This method must be called exactly once for each call to {@link #beginTransaction}.
 * </p>
 *
 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
 *
 * @throws IllegalStateException if there is no current transaction.
 * @throws SQLiteException if an error occurs.
 * @throws OperationCanceledException if the operation was canceled.
 *
 * @see #beginTransaction
 * @see #setTransactionSuccessful
 * @see #yieldTransaction
 */
public void endTransaction(CancellationSignal cancellationSignal) {
    throwIfNoTransaction();
    assert mConnection != null;

    endTransactionUnchecked(cancellationSignal, false);
}

private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
    if (cancellationSignal != null) {
        cancellationSignal.throwIfCanceled();
    }

    final SQLiteSession.Transaction top = mTransactionStack;
    // 当前 transaction 最终是否被认为成功要考虑两个方面:
    // 1. 当前 transaction 被标记为成功
    // 2. 当前 transaction 的子 transaction 同样被标记为成功
    boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;

    RuntimeException listenerException = null;
    final SQLiteTransactionListener listener = top.mListener;
    if (listener != null) {
        try {
            if (successful) {
                listener.onCommit(); // might throw
            } else {
                listener.onRollback(); // might throw
            }
        } catch (RuntimeException ex) {
            listenerException = ex;
            successful = false;
        }
    }
    // 注意此处,替换 mTransactionStack 为外层的 transaction
    mTransactionStack = top.mParent;
    /**
     * 1. 把 mTransactionPool 赋值给调用 endTransaction 时所在的 transaction 的 parent
     * 2. 把 mTransactionPool 赋值为调用 endTransaction 时所在的 transaction
     */
    recycleTransaction(top);

    if (mTransactionStack != null) {
        if (!successful) {
            mTransactionStack.mChildFailed = true;
        }
    } else {
        // mTransactionStack == null, 意味着当前是最外层的 transaction
        try {
            if (successful) {
                // 当前 transaction 成功及其子 transaction 也成功,则 COMMIT
                mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
            } else {
                // 否则回滚
                mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
            }
        } finally {
            // 已经是最外层 transaction,释放主连接
            releaseConnection(); // might throw
        }
    }

    if (listenerException != null) {
        throw listenerException;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容