32 Stimmen

Android: SQLite-Transaktionen bei Verwendung von ContentResolver

Das Ziel: Datenbank aus XML-Daten aktualisieren

Der Prozess:

  • Transaktion starten
  • Löschen alle vorhandenen Zeilen aus den Tabellen
  • Für jedes Hauptelement des geparsten XML einfügen. Zeile in die Haupttabelle und erhalten PK
  • Für jedes untergeordnete Element des Hauptelements einfügen. Datensatz in der 2. Tabelle mit FK aus dem vorherigen Schritt
  • Transaktion abschließen

Ziemlich normales Zeug, was Datenbankoperationen angeht. Das Problem ist, dass die CRUD-Operationen nicht innerhalb von ContentProvider sondern vielmehr mit ContentResolver Die Einfügung sieht also zum Beispiel so aus resolver.insert(CONTENT_URI, contentValues) . Die ContentResolver-API scheint keine transaktionsbezogenen Funktionen zu haben, und ich kann die bulkInsert da ich intermittierend in 2 Tabellen einfüge (und ich möchte, dass delete auch innerhalb der Transaktion).

Ich dachte daran, mein maßgeschneidertes Produkt zu registrieren. ContentProvider als Hörer durch Verwendung von registerContentObserver aber da ContentResolver#acquireProvider Methoden versteckt sind, wie erhalte ich die richtige Referenz?

Habe ich Pech?

2 Stimmen

0 Stimmen

Haben Sie jemals eine Lösung für dieses Problem gefunden? Ich kann keine Lösung finden, die funktioniert

41voto

Catalin Morosan Punkte 7781

Ich habe gesehen, dass im Quellcode der Google I/O-Anwendung, sie überschreiben ContentProvider 's applyBatch() Methode und verwenden Sie die darin enthaltenen Transaktionen. Sie erstellen also einen Stapel von ContentProviderOperation s und rufen dann getContentResolver().applyBatch(uri_authority, batch) .

Ich habe vor, diesen Ansatz zu verwenden, um zu sehen, wie er funktioniert. Ich bin neugierig, ob jemand anderes es ausprobiert hat.

7 Stimmen

Ich habe diesen Ansatz ausprobiert und er funktioniert gut. Allerdings sind alle ContentProviderOperationen im Stapel atomare Operationen. Was ich damit meine, ist, dass es keine Möglichkeit gibt, abhängige Operationen für Master-Detail-Beziehungen korrekt zu behandeln, bei denen der durch die erste Operation erstellte Identitätsschlüssel als Eingabe für nachfolgende Operationen benötigt wird. Ich habe diese Frage schon einmal gestellt, aber keine Antworten erhalten ( stackoverflow.com/questions/3224857/ ).

0 Stimmen

Ich habe es auch ausprobiert und einen Leistungsgewinn von über 1000 Prozent festgestellt. Einfach indem ich den Code aus dem IOShed-Projekt in meinen Provider kopiert habe.

0 Stimmen

Diese Antwort ist nicht richtig. Die Standardimplikation von applyBatch() iteriert nur über die Operationen und wendet sie isoliert an. Dies bietet lediglich die Möglichkeit, eine Transaktion zu implementieren, indem applyBatch() in Ihrem ContentProvider . Es bietet kein eigenständiges Transaktionsverhalten. Wenn Sie nicht die Kontrolle über die ContentProvider Umsetzung, haben Sie Pech gehabt.

15voto

David Burström Punkte 1583

Es ist möglich, transaktionsbasierte Multi-Table-Inserts seit Android 2.1 ziemlich sauber durchzuführen, indem man ContentProviderOperation verwendet, wie von kaciula erwähnt.

Wenn Sie das ContentProviderOperation-Objekt erstellen, können Sie .withValueBackReference(fieldName, refNr) aufrufen. Wenn die Operation mit applyBatch angewandt wird, hat dies zur Folge, dass dem ContentValues-Objekt, das mit dem insert()-Aufruf geliefert wird, eine Ganzzahl injiziert wird. Die Ganzzahl wird mit dem String fieldName verschlüsselt, und ihr Wert wird aus dem ContentProviderResult einer zuvor angewendeten ContentProviderOperation abgerufen, indiziert durch refNr.

Bitte beachten Sie das unten stehende Codebeispiel. In dem Beispiel wird eine Zeile in Tabelle 1 eingefügt, und die daraus resultierende ID (in diesem Fall "1") wird dann als Wert beim Einfügen der Zeile in Tabelle 2 verwendet. Der Kürze halber wird der ContentProvider nicht mit einer Datenbank verbunden. Im ContentProvider gibt es Ausdrucke, bei denen es sinnvoll wäre, das Transaktionshandling hinzuzufügen.

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
}

4voto

Bostone Punkte 35692

In Ordnung - damit dies nicht ziellos vor sich hin dümpelt: Die einzige Möglichkeit, die mir einfällt, ist, startTransaction und endTransaction als URL-basierte Abfrageanforderungen zu codieren. Etwas wie ContentResolver.query(START_TRANSACTION, null, null, null, null) . Dann in ContentProvider#query basierend auf dem registrierten URL-Aufruf Start oder Ende der Transaktion

0voto

Eric Woodruff Punkte 6134

Sie können die Implementierung des Content-Provider-Objekts selbst abrufen (wenn es sich im selben Prozess befindet, Hinweis: Sie können den Prozess des Providers mit multiprocess="true" oder process="" steuern http://developer.Android.com/guide/topics/manifest/provider-element.html ) mit ContentProviderClient.getLocalContentProvider (), die auf Ihre Provider-Implementierung übertragen werden kann, die zusätzliche Funktionen wie reset() zum Schließen und Löschen der Datenbank bieten kann, und Sie können auch eine benutzerdefinierte Transaktionsklasseninstanz mit save()- und close()-Methoden zurückgeben.

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 ());
}

Dann:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X