My goal
I want to insert multiple records into sqlite in batches (transactionally).
My question
I found the method android.content.ContentResolver
.bulkInsert(..) interesting but the javadoc states:
This function make no guarantees about the atomicity of the insertions.
Why would android provide a method that is crippled ? Can you name me usecases for non-atomic insertions ? I am going to obviously going to override ContentProvider.bulkInsert(..)
to ensure atomicity myself so I'm not sure why it is phrase like this.
The ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete) functions of persistent storage. A common pattern for accessing a ContentProvider from your UI uses a CursorLoader to run an asynchronous query in the background.
The ContentResolver. query() client method always returns a Cursor. This cursor contains the column specified by the query projection. The cursor object provides read access to rows and columns.
The Content Resolver behaves exactly as its name implies: it accepts requests from clients, and resolves these requests by directing them to the content provider with a distinct authority. To do this, the Content Resolver stores a mapping from authorities to Content Providers.
We need to override the bulk insert method like following...
public class Provider extends ContentProvider {
public static final Uri URI = Uri.parse("content://com.example.android.hoge/");
@Override
public String getType(Uri uri) {
return null;
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getReadableDatabase();
Cursor cursor = sdb.query(
Table.TABLENAME,
new String[]{Table.ID, Table.DATA, Table.CREATED},
selection,
selectionArgs,
null,
null,
sortOrder,
null
);
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
sdb.insert(Table.TABLENAME, null, values);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
/**
* super.bulkInsert is implemented the loop of insert without transaction
* So we need to override it and implement transaction.
*/
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
sdb.beginTransaction();
SQLiteStatement stmt = sdb.compileStatement(
"INSERT INTO `" + Table.TABLENAME + "`(`" + Table.DATA + "`, `" + Table.CREATED + "`) VALUES (?, ?);"
);
int length = values.length;
for(int i = 0; i < length; i++){
stmt.bindString(1, values[i].getAsString(Table.DATA));
stmt.bindLong(2, values[i].getAsLong(Table.CREATED));
stmt.executeInsert();
}
sdb.setTransactionSuccessful();
sdb.endTransaction();
return length;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
int rows = sdb.update(Table.TABLENAME, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Helper helper = Helper.getInstance(getContext(), null);
SQLiteDatabase sdb = helper.getWritableDatabase();
int rows = sdb.delete(Table.TABLENAME, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
private static class Helper extends SQLiteOpenHelper {
static Helper INSTANCE = null;
private Helper(Context context, CursorFactory factory) {
super(context, Table.FILENAME, factory, Table.VERSION);
}
public static Helper getInstance(Context context, CursorFactory factory) {
if (INSTANCE == null) {
INSTANCE = new Helper(context, factory);
}
return INSTANCE;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE `" + Table.TABLENAME + "`(" +
" `" + Table.ID + "` INTEGER PRIMARY KEY AUTOINCREMENT," +
" `" + Table.CREATED + "` INTEGER," +
" `" + Table.DATA + "` TEXT" +
");"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
Use applyBatch() instead.
This allows you to perform many different operations in a transactional manner, however there is a performance hit for this fexibility.
The relevant documentation can be found here in the ContentResolver SDK documentation
I have provided a quick tutorial on using applybatch in the symantics of backReferences
I also recommend looking at this question which discusses overriding applyBatch
This function make no guarantees about the atomicity of the insertions.
Correct me if I'm wrong but this is because we have no idea whether the given content provider overrides the bulkInsert()
method unless it is our own provider. If the bulkInsert() method is not overriden, default implementation will iterate over the values and call insert(Uri, ContentValues)
on each of them. It should be fine if you are using your own provider and know that you have implemented the bulkInsert()
method like following example and use the endTransaction()
method in finally block:
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch (match) {
case WEATHER:
db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
normalizeDate(value);
long _id = db.insert(WeatherEntry.TABLE_NAME,
null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
default:
return super.bulkInsert(uri, values);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With