Урок 38. Транзакции в SQLite. Использование SQLiteStatment

На этом уроке поговорим о транзакциях в БД, и о том, как с помощью транзакций и SQLiteStatment ускорить работу с базой данных SQLite в android.

Что такое транзакции в БД SqLite? В нескольких словах, это работа с данными по принципу –  «все или ничего». Например, если при сохранении в базу большого объема данных произошел сбой, и операция не закончилась успешно, сохранение или изменение данных в БД будет отменено, и все останется как было.
Т.е. либо транзакция выполняется целиком и переводит базу данных из одного целостного состояния в другое целостное состояние, либо, если по каким-либо причинам, одно из действий транзакции невыполнимо, или произошло какое-либо нарушение работы системы, база данных возвращается в исходное состояние, которое было до начала транзакции (происходит откат транзакции).
По умолчанию каждая инструкция SQL выполняется в собственной транзакции — это типичное поведение для базы данных SQL и SQLite не является исключением.
Но вы можете определить собственные границы транзакции, которые будут включать больше, чем одна инструкция, и это повышает производительность, поскольку одна большая транзакция выполняется  гораздо быстрее, чем множество маленьких.
SQLiteDatabase.beginTransaction();
try {
      //Вставляем данные
          SQLiteDatabase.setTransactionSuccessful();
} finally {
     SQLiteDatabase.endTransaction();
}
Семантика транзакций БД SqLite проста. Вы начинаете новую транзакцию путем вызова метода SQLiteDatabase.beginTransaction(). После того, как вы вставите все записи успешно, вызовите SQLiteDatabase. setTransactionSuccessful(), а затем завершаете транзакцию с SQLiteException.endTransaction(). Если что-то пойдет не так в одной из вставок, выпадет исключение SQLException,  и будет выполнен откат всех предыдущих вставок, поскольку не произойдет вызов метода SQLiteDatabase.setTransactionSuccessful().
Использование транзакций не только делает работу с БД более безопасной, но и существенно ускоряет ее.   Напишем небольшое приложение, которое поможет убедиться в этом.
Приложение будет создавать 1000 записей программно, выполнять  вставку записей в таблицу БД, и отображать длительность операции на экране.
Создаем новый проект с использованием шаблона Empty Activity.
В макете главного экрана такая структура:
< LinearLayout
    xmlns: android ="http://schemas.android.com/apk/res/android"
    xmlns: tools ="http://schemas.android.com/tools"
    android :layout_width= "match_parent"
    android :layout_height= "match_parent"
    android :orientation= "vertical"
    tools :context= "info.fandroid.sqlitestatement.MainActivity" >

    <TextView
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        android:text= "Time: "
        android:textSize= "24sp"
        android:id= "@+id/tvTime" />

    <Button
        android:layout_width= "wrap_content"
        android:layout_height= "wrap_content"
        android:text= "Insert"
        android:id= "@+id/btnInsert" />

</LinearLayout >
Текстовое поле, в которое мы будем выводить время длительности втавки данных в БД.
И кнопка, по нажатию которой будет происходить вставка данных.
package ...

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String DB_NAME = "MyDB" ;
    private static final String TABLE_NAME = "MyTable" ;
    private SQLiteDatabase database;
    TextView tvTime ;
    Button btnInsert ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout. activity_main);
        initDB();
        tvTime = (TextView) findViewById(R.id. tvTime );
        btnInsert = (Button) findViewById(R.id. btnInsert );
        btnInsert.setOnClickListener( this);

    }

    private void initDB(){
        database = this.openOrCreateDatabase( DB_NAME , MODE_PRIVATE , null );
        database.execSQL( "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(FirstNumber INT, SecondNumber INT, Result INT);" );
        database.delete( TABLE_NAME, null , null );
    }

       @Override
    public void onClick(View view) {
           database.delete( TABLE_NAME, null , null );
           long startTime = System. currentTimeMillis();
           insertRecords();
           long diff = System. currentTimeMillis() - startTime;
           tvTime.setText( "Time: " + Long. toString(diff) + " ms");

    }

    private void insertRecords(){
        for ( int i = 0; i < 1000 ; i++){
            ContentValues cv = new ContentValues();
            cv.put( "FirstNumber", i);
            cv.put( "SecondNumber", i);
            cv.put( "Result", i*i);
            database.insert( TABLE_NAME , null , cv);
        }
    }

    @Override
    protected void onDestroy() {
        database.close();
        super.onDestroy();
    }
}
В файле MainActivity.java реализуем интерфейс слушателя нажатия кнопки.
Далее добавим константы имени БД  и заголовка таблицы, переменную класса SQLiteDatabase.
Объявим кнопку и текстовое поле.
Найдем экранные компоненты по id, присвоим слушатель кнопке.
Теперь напишем метод initDB в котором будем создавать БД. Метод openOrCreateDatabase создает БД или открывает, если она уже создана.
Затем выполняем запрос на создание таблицы с темя столбцами, где два столбца будут хранить целые числа, а третий столбец – результат их произведения.
Также добавим метод очистки всех полей таблицы.
Будем вызывать метод initDB  в onCreate.
Теперь заполним метод onClick – сначала вызываем метод очистки таблицы, затем создаем переменную startTime, которая будет хранить  приблизительный момент нажатия кнопки.
Здесь вызываем метод добавления записей в БД. Все будет происходить в основном потоке, и процесс вставки записей будет занимать какое-то время.
После чего мы снова создаем переменную, в которую передаем разницу между текущим временем и моментом нажатия кнопки.
И затем выводим результат в текстовое поле, преобразовав время в строковый формат.
Теперь рассмотрим код метода вставки данных в БД.  Ничего нового здесь по сравнению с прошлыми уроками нет.
В цикле создаем экземпляр класса ContentValues, добавляем данные в столбцы методом put – первое значение, второе значение, и их произведение. Метод insert записывает данные в таблицу.
Теперь запустим приложение в эмуляторе.
Нажимаем кнопку, наше приложение зависает на несколько секунд, а затем сообщает, что операция вставки занимает 5071 миллисекунду.
Добавим методы транзакции в метод insertRecords().
private void insertRecords(){
    database.beginTransaction();
    try {
        for ( int i = 0; i < 1000 ; i++){
            ContentValues cv = new ContentValues();
            cv.put( "FirstNumber", i);
            cv.put( "SecondNumber", i);
            cv.put( "Result", i*i);
            database.insert( TABLE_NAME , null , cv);
        }
        database.setTransactionSuccessful();
    } finally {
        database.endTransaction();
    }
}
Метод beginTransaction() начинает транзакцию, метод setTransactionSuccessful() вызовется по ее окончании. Его и весь блок цикла обернем в конструкцию try> finally, для этого выделяем код и нажимаем клавиши Ctrl+Alt+T. Выбираем нужное в списке. Завершаем транзакцию методом endTransaction().
Запустим приложение и нажмем кнопку вставки.
C применением транзакций длительность операции вставки сократилась до 115 ms – это в 44 раза быстрее!
Но это еще не все – результат можно улучшить, если использовать класс SQLiteStatement.
Перепишем метод insertRecords:
private void insertRecords(){
    String sql = "INSERT INTO " + TABLE_NAME + " VALUES(?,?,?);";
    SQLiteStatement statement = database.compileStatement(sql);
    database .beginTransaction();
    try {
        for ( int i = 0; i < 1000 ; i++){

            statement.clearBindings();
            statement.bindLong( 1, i);
            statement.bindLong( 2, i);
            statement.bindLong( 3, i*i);
            statement.execute();
        }
        database.setTransactionSuccessful();
    } finally {
        database.endTransaction();
    }
}
Создаем строковую переменную, которая будет хранить sql инструкцию. Далее создаем экземпляр SQLiteStatement, которому передаем скомпилированный SQL запрос.
Меняем код в теле цикла на такой: сначала метод clearBindings() очищает все текущие привязки, методы bindLong привязывают значения данных к столбцам, а метод execute выполняет вставку данных.
Запускаем приложение, жмем кнопку вставки.
Результат – 42 ms, то есть мы ускорили операцию вставки данных еще почти в 3 раза.
Как видите, использование класса SQLiteStatment в сочетании с транзакциями позволяет существенно ускорить  вставку данных в БД.

<<Предыдущий урок     Следующий урок>>

Коментарі: 1
Додати коментар