середу, 22 серпня 2012 р.

Пишем свой SQLiteOpenHelper

И так сегодня поговорим о вспомогательном классе SQLiteOpenHelper для работы с SQLite. В этой теме нет ничего сложного, думаю будет всё просто и понятно. Начнём.
Для начала следует описать свой класс-контейнер для удобного хранения данных:

public class Shop {
 
 private int id;
 private String title;
 private double price;
 
 public Shop() {
 
 }
 
 public Shop(String _title, double _price) {
  title = _title;
  price = _price;
 }
 
 public Shop(int _id, String _title, double _price) {
  id = _id;
  title = _title;
  price = _price;
 }
 
 public void setId(int _id) {
  id = _id;
 }
 
 public void setTitle(String _title) {
  title = _title;
 }
 
 public void setPrice(double _price) {
  price = _price;
 }
 
 public int getId() { return id; }
 
 public String getTitle() { return title; }
 
 public double getPrice() { return price; }
}

Главное что бы в классе присутствовало поле id - это будет id из базы данных. Для наглядности в нашем классе присутствует ещё два поля с названием товара и его ценой.
Ещё создадим класс где будут описаны константы. Замечу что это одна из моих прихоть(хоть это является даже не моей прихотью, а просто это удобно так делать), так что вы можете описать эти константы в классе расширяющего SQLiteOpenHelper.

public class AppData {
 
 public final static int DATABASE_VERSION = 1;
 public final static String DATABASE_NAME = "shopDB.sqlite3";
 public final static String TABLE_NAME = "shop";
 public final static String COLUMN_ID = "_id";
 public final static String COLUMN_TITLE = "title";
 public final static String COLUMN_PRICE = "price";
}

Всё просто и не требует лишних комментариев, так как из названия полей можно понять для чего они.
Далее создаём класс который наследуется от SQLiteOpenHelper. Сразу он будет подчёркнутым красным потому что не реализованы два основных метода и конструктор. Eclipse сам создаст необходимые минимальные методы для функционирования класса.
Конструктор по умолчанию оставляем как есть:

 public DBHelper(Context context, String name, CursorFactory factory, int version) {
  super(context, name, factory, version);
 }

Если вам что нибудь нужно ещё то передавайте в конструктор и выделяйте отедльное поле. Но самое главное в конструкторе вызвать наследуемый конструктор.

@Override
public void onCreate(SQLiteDatabase db) {
 String createShopTable = "CREATE TABLE " + AppData.TABLE_NAME + " (" + AppData.COLUMN_ID +
  " INTEGER PRIMARY KEY AUTOINCREMENT, " + AppData.COLUMN_TITLE + " TEXT NOT NULL, " +
  AppData.COLUMN_PRICE + " DOUBLE NOT NULL)";
 db.execSQL(createShopTable);
}

Этот метод обязателен, он создаёт таблицу если её не существует. Те кто не знаком с основами языка SQL или его диалектами тому наверное трудно будет понять что написано в переменной createShopTable. В этоме запросе создаётся таблица с тремя полями: _id (именно с подчёркиванием так обязатльно, это написано в документации, айди всегда с подчёркиванием) это порядковый номер строки таблицы и является идентификатором/счётчиком; title - название товара; price - цена товара.

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 if(AppData.DATABASE_VERSION < newVersion) {
  db.execSQL("DROP TABLE IF EXISTS " + AppData.TABLE_NAME);
  onCreate(db);
 }
}

Ещё один обязательный метод onUpgrade. Он создаёт саму базу данных если её ещё не существует. В метод так же передаётся версии бд, если передаваемая версия выше существубющей то бд пересаздаётся.
Главные составляющие бд мы уже описали, и она является работоспособной. Но она малофункциональна и наш класс может лишь создавать бд и таблицу. В любой активити или классе мы можем получить экземпляр класса и выполнить любой SQL запрос, но это лишние "движения" и мы реализуем как можно больше методов для удобства в нашем классе, ведь мы его создавали в первую очередь для удобства!
Первым делом предлагаю сделать что бы класс сам добавлял данные в бд. Для этого напишем вот такой вот не трудный метод:

 public void addShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  db.insert(AppData.TABLE_NAME, null, cv);
  db.close();
 }
В метод передаётся объект который следует добавить в бд. Открываем бд для записи, определяем объект ContentValues в который будем ложить данные для записи в методе put. В метод put первый аргумент название столбца в бд, второй - данные. Вызываем метод insert в который следует передать название таблицы, второй параметр оставляем нулевым так как он нужен если не ошибаюсь для того если мы пытаемся вставить пустую строку в SQL, третий параметр это ContentValues с данными. И в конце стоит закрыть соединение с бд. Кстати метод insert возвращает long единицу если операция прошла успешно и ноль  если нет. Так что можете ещё проверять были ли вставленны данные в бд.
Далее розумно думаю написать метод который по id вытаскивает нужный объект.

 public Shop getShop(int id) {
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.query(AppData.TABLE_NAME, new String[] { AppData.COLUMN_ID, AppData.COLUMN_TITLE, AppData.COLUMN_PRICE },
    AppData.COLUMN_ID + "=?", new String[] { String.valueOf(id) }, null, null, null);
  if(cursor != null)
   cursor.moveToFirst();
  Shop shop = new Shop(Integer.parseInt(cursor.getString(0)), cursor.getString(1), Double.parseDouble(cursor.getString(2)));
  return shop;
 }

Метод должен возвращать объект. В метод передаём айди необходимого объекта. Сначала открывает бд для чтения. далее определяем Cursor. Здесь следует по подробней остановится так как это важная часть. В Cursor мы получаем результат запроса. Главное что здесь нужно, так это правильно заполнить метод query. Первый параметр это название таблицы, второй параметр - массив столбцов которые следует вернуть в результате, третий параметр - название столбца для запроса со знаком "=?", четвёртый параметр - массив сравниваемых данных, остальные параметры оставляем нулевыми (они используются для группирования результата). Если объяснять запрос на пальцех то говорится так: "верни мне  из таблицы shop значение столбцов _id, title, price где _id будет равен входному числу". Далее првоеряем Cursor не равен он нулю, если нулевый то результат не вернул или же ошибочный запрос. Объявляем объект Shop в конструктор которого передаём полученные данные. Данные из Cursor вытягиваются по индексам. Индексы являются столбцы которые мы указывали в методе query и именно в том порядке в каком мы указали. Возвратили объект.
Следующим шагом будет удаление необходимых данных с бд:

 public int deleteShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  return db.delete(AppData.TABLE_NAME, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }

В метод передаём удаляемый объект. Традиционно открываем соединение с бд на запись. Далее выполняем команду delete которая позволяет удалять данные. Входными данными этого метода является: первый параметр - название таблицы; второй параметр - название столбца с _id и строка "=?"; третий параметр - массив с сравниваемыми данными. На пальцах запрос звучит так: "удали мне данные из таблицы shop где столбец _id равен значению shop.getId()". Метод возвращает результат запроса - один если удачно и ноль если fail.
Неплохо было бы реализовать метод который обновляет необходимые нам данные:

public int updateShop(Shop shop) {
 SQLiteDatabase db = this.getWritableDatabase();
 ContentValues cv = new ContentValues();
 cv.put(AppData.COLUMN_TITLE, shop.getTitle());
 cv.put(AppData.COLUMN_PRICE, shop.getPrice());
 return db.update(AppData.TABLE_NAME, cv, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
}

Входным параметром является объект который следует обновить. Открываем бд на запись. Определяем ContentValues, кладём в него обновлённые данные. Выполняем запрос update с такими входными параметрами: первый - название таблицы; второй - объект ContentValues, тертий - условие (в нашем случае это _id который равняется следующим параметрам), четвёртый - параметры удовлетворяющие условия третьего параметра. На пальцах: "вот тебе новые данные и вставь их в таблицу shop где _id равняется shop.getId()".
Наш вспомогательный класс всё разрастается и разрастается и следующим полезным методом будет метод который возврощает количество данных бд:

public int getShopCount() {
 String query = "SELECT * FROM " + AppData.TABLE_NAME;
 SQLiteDatabase db = this.getReadableDatabase();
 Cursor cursor = db.rawQuery(query, null);
 cursor.close();
 return cursor.getCount();
}

Здесь всё просто. Формируем простой запрос который возвращает нам все данные из таблицы shop. Открываем соединение с бд на чтение. Выполняем команду rawQuery в которой указываем в качестве входных параметров запрос и нул (вторым параметром являются данные сравнения которые в данном запросе нам не нужны). В ответ получаем Cursor с результатами и возвращаем количество полей таблицы.
Для полного удобства предлагаю ещё написать один метод который возвращает уже готовый список с объектами из бд:

public List<Shop> getAllShop() {
 List<Shop> shopList = new ArrayList<Shop>();
 String query = "SELECT * FROM " + AppData.TABLE_NAME;
 SQLiteDatabase db = this.getReadableDatabase();
 Cursor cursor = db.rawQuery(query, null);
 if(cursor.moveToFirst()) {
  do {
   Shop shop = new Shop();
   shop.setId(Integer.parseInt(cursor.getString(0)));
   shop.setTitle(cursor.getString(1));
   shop.setPrice(Double.parseDouble(cursor.getString(2)));
   shopList.add(shop);
  } while(cursor.moveToNext());
 }
 return shopList;
}

И так, определяем список объектов Shop. Формируем запрос который возвращает все данные из таблицы shop. Подключаемся к бд с правами на чтение. Возвращаем все данные в Cursor. Если в cursor'e есть данные то организовываем цикл по нему в котором кладём в объект Shop данные из cursor'a. Далее этот объект shop добавляем коллекцию и в конце возвращаем коллекцию с объектами.
Ну и на последок напишу ещё метод который возвращает Cursor с данными всей таблицы, это бывает полезно когда используется список с такими адаптерами как SimpleCursorAdapter:

public Cursor getAllCursor() {
 SQLiteDatabase db = this.getReadableDatabase();
 return db.query(AppData.TABLE_NAME, null, null, null, null, null, null);
}

Целиком класс будет выглядеть так:

import java.util.ArrayList;
import java.util.List;
 
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
 
public class DBHelper extends SQLiteOpenHelper {
 
 public DBHelper(Context context, String name, CursorFactory factory, int version) {
  super(context, name, factory, version);
 }
 
 @Override
 public void onCreate(SQLiteDatabase db) {
  String createShopTable = "CREATE TABLE " + AppData.TABLE_NAME + " (" + AppData.COLUMN_ID +
    " INTEGER PRIMARY KEY AUTOINCREMENT, " + AppData.COLUMN_TITLE + " TEXT NOT NULL, " +
    AppData.COLUMN_PRICE + " DOUBLE NOT NULL)";
  db.execSQL(createShopTable);
 }
 
 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  if(AppData.DATABASE_VERSION < newVersion) {
   db.execSQL("DROP TABLE IF EXISTS " + AppData.TABLE_NAME);
   onCreate(db);
  }
 }
 
 public List<Shop> getAllShop() {
  List<Shop> shopList = new ArrayList<Shop>();
  String query = "SELECT * FROM " + AppData.TABLE_NAME;
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.rawQuery(query, null);
  if(cursor.moveToFirst()) {
   do {
    Shop shop = new Shop();
    shop.setId(Integer.parseInt(cursor.getString(0)));
    shop.setTitle(cursor.getString(1));
    shop.setPrice(Double.parseDouble(cursor.getString(2)));
    shopList.add(shop);
   } while(cursor.moveToNext());
  }
  return shopList;
 }
 
 public void addShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  db.insert(AppData.TABLE_NAME, null, cv);
  db.close();
 }
 
 public Shop getShop(int id) {
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.query(AppData.TABLE_NAME, new String[] { AppData.COLUMN_ID, AppData.COLUMN_TITLE, AppData.COLUMN_PRICE },
    AppData.COLUMN_ID + "=?", new String[] { String.valueOf(id) }, null, null, null);
  if(cursor != null)
   cursor.moveToFirst();
  Shop shop = new Shop(Integer.parseInt(cursor.getString(0)), cursor.getString(1), Double.parseDouble(cursor.getString(2)));
  return shop;
 }
 
 public int updateShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  return db.update(AppData.TABLE_NAME, cv, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }
 
 public int deleteShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  return db.delete(AppData.TABLE_NAME, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }
 
 public int getShopCount() {
  String query = "SELECT * FROM " + AppData.TABLE_NAME;
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.rawQuery(query, null);
  cursor.close();
  return cursor.getCount();
 }
 
 public Cursor getAllCursor() {
  SQLiteDatabase db = this.getReadableDatabase();
  return db.query(AppData.TABLE_NAME, null, null, null, null, null, null);
 }
 
}

А теперь пришо время проверить работоспособность сего чуда. В главном активити прописываем и вызываем:

    private void addToShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     db.addShop(new Shop("Сапоги", 283.23));
     db.addShop(new Shop("Куртка", 683.28));
     db.addShop(new Shop("Шапка", 99.99));
     db.addShop(new Shop("Штаны", 160));
     db.addShop(new Shop("Футболка", 29.99));
     db.addShop(new Shop("Очки", 65.50));
    }
 
    private void deleteShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     List<Shop> shopList = db.getAllShop();
     int result = 0;
     for(Shop shop : shopList) {
      if(shop.getId() == 3)
       result = db.deleteShop(shop);
     }
     if(result == 1)
      Log.i("MyTag", "запись усшено удалена");
     else
      Log.i("MyTag", "при удалении произошла ошибка");
    }
 
    private void readShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     List<Shop> shopList = db.getAllShop();
     for(Shop shop : shopList) {
      Log.i("MyTag", "ID = " + shop.getId() + " && TITLE = " + shop.getTitle() + " && PRICE = " + shop.getPrice());
     }
    }
Можете прикрутить это к любой View или же выводить для наглядности в Log как я сделал, а лучше всего в ListView (этому будет посвящён следующий урок).
Всем спасибо, с ув. Вячеслав.
Скачать полностью проект можно по ссылкам:

1 коментар:

  1. Ужас SELECT * FROM table для получения количества записей...

    SELECT COUNT(DISTINCT column) AS co FROM foo

    В нашем случае поле id и так уникально поэтому
    SELECT COUNT(id) AS c FROM shop LIMIT 1

    ВідповістиВидалити