Androide:Transacciones SQLite al usar ContentResolver
Pregunta
La meta:actualizar la base de datos a partir de datos XML
El proceso:
- Iniciar transacción
- Borrar todas las filas existentes de las tablas
- Por cada elemento principal del XML analizado insertar fila en la tabla principal y obtener PK
- Por cada hijo del elemento principal. insertar registre en la segunda tabla proporcionando FK del paso anterior
- Confirmar transacción
Cosas bastante estándar en cuanto a operaciones de base de datos.El problema es que las operaciones CRUD no se realizan dentro ContentProvider
sino más bien usando ContentResolver
entonces el inserto, por ejemplo, se ve así resolver.insert(CONTENT_URI, contentValues)
.La API ContentResolver no parece tener nada relacionado con la transacción y no puedo usarla. bulkInsert
ya que estoy insertando en 2 tablas de forma intermitente (además quiero tener delete
dentro de la transacción también).
Estaba pensando en registrar mi personalizado. ContentProvider
como oyente usando registerContentObserver
pero desde ContentResolver#acquireProvider
Los métodos están ocultos ¿Cómo obtengo la referencia correcta?
¿No tengo suerte?
Solución
He visto que en el código fuente de la aplicación Google I / O, que anulan las transacciones método ContentProvider
y uso de applyBatch()
dentro de ella. Así, se crea un lote de ContentProviderOperation
s y luego llamar getContentResolver().applyBatch(uri_authority, batch)
.
Tengo la intención de utilizar este enfoque para ver cómo funciona. Tengo curiosidad por si alguien más ha probado.
Otros consejos
Es posible realizar inserciones de múltiples tablas basadas en transacciones de manera bastante limpia desde Android 2.1 usando ContentProviderOperation, como lo menciona kaciula.
Cuando crea el objeto ContentProviderOperation, puede llamar a .withValueBackReference(fieldName, refNr).Cuando la operación se aplica usando applyBatch, el resultado es que al objeto ContentValues que se proporciona con la llamada insert() se le inyectará un número entero.El número entero se codificará con la cadena fieldName y su valor se recuperará del ContentProviderResult de una ContentProviderOperation aplicada previamente, indexada por refNr.
Consulte el ejemplo de código a continuación.En el ejemplo, se inserta una fila en la tabla1 y el ID resultante (en este caso "1") se usa como valor al insertar la fila en la tabla 2.Para abreviar, ContentProvider no está conectado a una base de datos.En ContentProvider, hay impresiones donde sería adecuado agregar el manejo de transacciones.
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
}
Muy bien, entonces esto no se queda sin rumbo:la única forma que se me ocurre es codificar startTransaction y endTransaction como solicitudes de consulta basadas en URL.Algo como ContentResolver.query(START_TRANSACTION, null, null, null, null)
.Luego en ContentProvider#query
basado en la URL registrada, llame al inicio o final de la transacción
Puede obtener la aplicación del propio objeto proveedor de contenido (si está en el mismo proceso, pista: se puede controlar el proceso del proveedor con multiproceso = "true" o proceso = "" http://developer.android.com/guide/topics/manifest/provider-element.html ) usando ContentProviderClient.getLocalContentProvider (), que puede ser moldeado a su proveedor de aplicación que puede proporcionar una funcionalidad adicional como un reset () que se cierra y elimina la base de datos y también se puede devolver una instancia de clase de transacciones a medida con Save () y close () métodos.
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 ());
}
A continuación:
ContentProviderClient client = getContentResolver ().acquireContentProviderClient (Contract.authorityLocal);
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider ()).createTransaction ();