7

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.

Frankie Ribery
  • 11,933
  • 14
  • 50
  • 64

4 Answers4

4

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) {
        }
    }
}
Mitsuaki Ishimoto
  • 3,162
  • 2
  • 25
  • 32
2

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

Community
  • 1
  • 1
Moog
  • 10,193
  • 2
  • 40
  • 66
1

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);
        }
    }
Yogesh Umesh Vaity
  • 41,009
  • 21
  • 145
  • 105
0

First Add method for bulk insert in your content provider

 @Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
    switch (uriMatcher.match(uri)) {
        case USERS:
            for (ContentValues value : values) {
                long rowID = sqLiteDatabase.insert(YOUR_TABLENAME, "", value);
                if (rowID > 0) {
                    Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);  //append ID into CONTENT_URI
                    getContext().getContentResolver().notifyChange(_uri, null);
                    return values.length;    //return total number of data inserted
                }
            }
            break;
    }
    return super.bulkInsert(uri, values);
}

Add below code in button click (Below is executed to insert bulk data)

            String userName = editTextUserName.getText().toString();
            String userCity = editTextUserCity.getText().toString();

            Log.d("BulkInsert", "onClick: -------START------");
            ContentValues[] contentValue = new ContentValues[5000];
            for (int i = 0; i < 5000; i++) {

                contentValue[i] = new ContentValues();  // initialize Array of content values

                //store data in content values object
                contentValue[i].put(UserModel.USER_CITY, userCity);
                contentValue[i].put(UserModel.USER_NAME, userName);
                contentValue[i].put(UserModel.USER_PINCODE, userPincode);
            }
            int count = getContentResolver().bulkInsert(YOUR_TABLE_URI, contentValue);   //insert data
            Log.d("BulkInsert", "onClick: " + count);  //Display number of data inserted
            Log.d("BulkInsert", "onClick: -------STOP------");
Khyati Vara
  • 1,042
  • 13
  • 22