Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of ContentResolver.bulkInsert(..)?

Tags:

android

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.

like image 808
Frankie Ribery Avatar asked Jan 18 '11 15:01

Frankie Ribery


People also ask

What is the use of ContentResolver in Android?

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.

What does ContentResolver query () return?

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.

What is the relation between content provider and content resolver in Android applications?

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.


3 Answers

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) {
        }
    }
}
like image 137
Mitsuaki Ishimoto Avatar answered Oct 28 '22 15:10

Mitsuaki Ishimoto


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

like image 29
Moog Avatar answered Oct 28 '22 14:10

Moog


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);
        }
    }
like image 1
Yogesh Umesh Vaity Avatar answered Oct 28 '22 15:10

Yogesh Umesh Vaity