Android:使用 ContentResolver 时的 SQLite 事务

发布于 2024-08-21 05:43:05 字数 724 浏览 11 评论 0原文

目标:从 XML 数据刷新数据库

过程:

  • 启动事务
  • 删除表中的所有现有行
  • 根据解析的 XML 插入行的每个主要元素进入主表并获取 PK
  • 每个主元素的每个子元素 插入 记录到第二个表中,提供上一步的 FK
  • 提交事务

就数据库操作而言,这是相当标准的东西。问题是 CRUD 操作不是在 ContentProvider 内完成的,而是使用 ContentResolver 完成的,因此插入示例看起来像 resolver.insert(CONTENT_URI, contentValues)。 ContentResolver API 似乎没有与事务相关的任何内容,并且我无法使用 bulkInsert 因为我间歇性地插入 2 个表(另外我希望在交易也是如此)。

我正在考虑使用 registerContentObserver 将自定义的 ContentProvider 注册为侦听器,但由于 ContentResolver#acquireProvider 方法被隐藏,我如何获得正确的引用?

我运气不好吗?

The goal: refresh database from XML data

The process:

  • Start transaction
  • Delete all existing rows from the tables
  • Per each main element of parsed XML insert row into main table and get PK
  • Per each child of the main element insert record into 2nd table providing FK from the previous step
  • Commit transaction

Pretty standard stuff as far as db operations. The problem is that CRUD operations are not done within ContentProvider but rather using ContentResolver so the insert for example looks like resolver.insert(CONTENT_URI, contentValues). The ContentResolver API doesn't seem to have anything pertained to transaction and I cannot use bulkInsert since I'm inserting in 2 tables intermittently (plus I want to have delete inside the transaction as well).

I was thinking of registering my customized ContentProvider as listener by using registerContentObserver but since ContentResolver#acquireProvider methods are hidden how do I obtain the right reference?

Am I out of luck?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

东京女 2024-08-28 05:43:05

我在 Google I/O 应用程序的源代码中看到,它们重写了 ContentProviderapplyBatch() 方法并在其中使用事务。因此,您创建一批 ContentProviderOperation ,然后调用 getContentResolver().applyBatch(uri_authority, batch)

我打算使用这种方法来看看它是如何工作的。我很好奇是否还有其他人尝试过。

I've seen that in the source code of Google I/O application, they override ContentProvider's applyBatch() method and use transactions inside of it. So, you create a batch of ContentProviderOperation s and then call getContentResolver().applyBatch(uri_authority, batch).

I'm planning to use this approach to see how it works. I'm curious if anyone else has tried it.

窝囊感情。 2024-08-28 05:43:05

正如 kaciula 提到的,自 Android 2.1 以来,可以通过使用 ContentProviderOperation 相当干净地进行基于事务的多表插入。

构建 ContentProviderOperation 对象时,可以调用 .withValueBackReference(fieldName, refNr)。当使用 applyBatch 应用该操作时,结果是通过 insert() 调用提供的 ContentValues 对象将注入一个整数。该整数将使用 fieldName 字符串作为键控,其值是从先前应用的 ContentProviderOperation 的 ContentProviderResult 中检索的,由 refNr 索引。

请参考下面的代码示例。在示例中,在表 1 中插入一行,然后在表 2 中插入该行时,将生成的 ID(在本例中为“1”)用作值。为简洁起见,ContentProvider 未连接到数据库。在 ContentProvider 中,有适合添加事务处理的打印输出。

public class BatchTestActivity extends Activity {
    /** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ArrayList<ContentProviderOperation> list = new
            ArrayList<ContentProviderOperation>();

        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.FIRST_URI).build());
        ContentValues cv = new ContentValues();
        cv.put("name", "second_name");
        cv.put("refId", 23);

        // In this example, "refId" in the contentValues will be overwritten by
        // the result from the first insert operation, indexed by 0
        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.SECOND_URI).
            withValues(cv).withValueBackReference("refId", 0).build());

        try {
            getContentResolver().applyBatch(
                BatchContentProvider.AUTHORITY, list);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        }
    }
}

public class BatchContentProvider extends ContentProvider {

    private static final String SCHEME = "content://";
    public static final String AUTHORITY = "com.test.batch";

    public static final Uri FIRST_URI =
        Uri.parse(SCHEME + AUTHORITY + "/" + "table1");
    public static final Uri SECOND_URI =
        Uri.parse(SCHEME + AUTHORITY + "/" + "table2");


    public ContentProviderResult[] applyBatch(
        ArrayList<ContentProviderOperation> operations)
            throws OperationApplicationException {
        System.out.println("starting transaction");
        ContentProviderResult[] result;
        try {
            result = super.applyBatch(operations);
        } catch (OperationApplicationException e) {
            System.out.println("aborting transaction");
            throw e;
        }
        System.out.println("ending transaction");
        return result;
    }

    public Uri insert(Uri uri, ContentValues values) {
        // this printout will have a proper value when
        // the second operation is applied
        System.out.println("" + values);

        return ContentUris.withAppendedId(uri, 1);
    }

    // other overrides omitted for brevity
}

It is possible to do transaction based multi table inserts rather cleanly since Android 2.1 by using ContentProviderOperation, as mentioned by kaciula.

When you build the ContentProviderOperation object, you can call .withValueBackReference(fieldName, refNr). When the operation is applied using applyBatch, the result is that the ContentValues object that is supplied with the insert() call will have an integer injected. The integer will be keyed with the fieldName String, and its value is retrieved from the ContentProviderResult of a previously applied ContentProviderOperation, indexed by refNr.

Please refer to the code sample below. In the sample, a row is inserted in table1, and the resulting ID (in this case "1") is then used as a value when inserting the row in table 2. For brevity, the ContentProvider is not connected to a database. In the ContentProvider, there are printouts where it would be suitable to add the transaction handling.

public class BatchTestActivity extends Activity {
    /** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ArrayList<ContentProviderOperation> list = new
            ArrayList<ContentProviderOperation>();

        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.FIRST_URI).build());
        ContentValues cv = new ContentValues();
        cv.put("name", "second_name");
        cv.put("refId", 23);

        // In this example, "refId" in the contentValues will be overwritten by
        // the result from the first insert operation, indexed by 0
        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.SECOND_URI).
            withValues(cv).withValueBackReference("refId", 0).build());

        try {
            getContentResolver().applyBatch(
                BatchContentProvider.AUTHORITY, list);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        }
    }
}

public class BatchContentProvider extends ContentProvider {

    private static final String SCHEME = "content://";
    public static final String AUTHORITY = "com.test.batch";

    public static final Uri FIRST_URI =
        Uri.parse(SCHEME + AUTHORITY + "/" + "table1");
    public static final Uri SECOND_URI =
        Uri.parse(SCHEME + AUTHORITY + "/" + "table2");


    public ContentProviderResult[] applyBatch(
        ArrayList<ContentProviderOperation> operations)
            throws OperationApplicationException {
        System.out.println("starting transaction");
        ContentProviderResult[] result;
        try {
            result = super.applyBatch(operations);
        } catch (OperationApplicationException e) {
            System.out.println("aborting transaction");
            throw e;
        }
        System.out.println("ending transaction");
        return result;
    }

    public Uri insert(Uri uri, ContentValues values) {
        // this printout will have a proper value when
        // the second operation is applied
        System.out.println("" + values);

        return ContentUris.withAppendedId(uri, 1);
    }

    // other overrides omitted for brevity
}
梦魇绽荼蘼 2024-08-28 05:43:05

好吧 - 所以这不会漫无目的:我能想到的唯一方法是将 startTransaction 和 endTransaction 编码为基于 URL 的查询请求。类似于 ContentResolver.query(START_TRANSACTION, null, null, null, null)。然后在ContentProvider#query中根据注册的URL调用开始或结束事务

All right - so this does not dingle aimlessly: the only way I can think of is to code startTransaction and endTransaction as URL-based query requests. Something like ContentResolver.query(START_TRANSACTION, null, null, null, null). Then in ContentProvider#query based on the registered URL call start or end transaction

你曾走过我的故事 2024-08-28 05:43:05

您可以获取内容提供程序对象本身的实现(如果在同一进程中,提示:您可以使用 multiprocess="true" 或 process="" http://developer.android.com/guide/topics/manifest/provider-element.html) 使用 ContentProviderClient.getLocalContentProvider () 可以转换为您的提供程序实现,它可以提供额外的功能,例如关闭和删除数据库的 Reset(),您还可以使用 save() 和 close() 方法返回自定义 Transaction 类实例。

public class Transaction {
    protected Transaction (SQLiteDatabase database) {
        this.database = database;
        database.beginTransaction ();
    }

    public void save () {
        this.database.setTransactionSuccessful ();
    }

    public void close () {
        this.database.endTransaction ();
    }

    private SQLiteDatabase database;
}

public Transaction createTransaction () {
    return new Transaction (this.dbHelper.getWritableDatabase ());
}

然后:

ContentProviderClient client = getContentResolver ().acquireContentProviderClient (Contract.authorityLocal);
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider ()).createTransaction ();

You can get the implementation of the content provider object itself (if in the same process, hint: you can control the provider's process with multiprocess="true" or process="" http://developer.android.com/guide/topics/manifest/provider-element.html) using ContentProviderClient.getLocalContentProvider () which can be casted to your provider implementation which can provide extra functionality like a reset() that closes and deletes the database and you can also return a custom Transaction class instance with save() and close() methods.

public class Transaction {
    protected Transaction (SQLiteDatabase database) {
        this.database = database;
        database.beginTransaction ();
    }

    public void save () {
        this.database.setTransactionSuccessful ();
    }

    public void close () {
        this.database.endTransaction ();
    }

    private SQLiteDatabase database;
}

public Transaction createTransaction () {
    return new Transaction (this.dbHelper.getWritableDatabase ());
}

Then:

ContentProviderClient client = getContentResolver ().acquireContentProviderClient (Contract.authorityLocal);
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider ()).createTransaction ();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文