И так сегодня поговорим о вспомогательном классе 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.
Ещё создадим класс где будут описаны константы. Замечу что это одна из моих прихоть(хоть это является даже не моей прихотью, а просто это удобно так делать), так что вы можете описать эти константы в классе расширяющего 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"; }
Всё просто и не требует лишних комментариев, так как из названия полей можно понять для чего они.
Конструктор по умолчанию оставляем как есть:
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 запрос, но это лишние "движения" и мы реализуем как можно больше методов для удобства в нашем классе, ведь мы его создавали в первую очередь для удобства!
Первым делом предлагаю сделать что бы класс сам добавлял данные в бд. Для этого напишем вот такой вот не трудный метод:
Главные составляющие бд мы уже описали, и она является работоспособной. Но она малофункциональна и наш класс может лишь создавать бд и таблицу. В любой активити или классе мы можем получить экземпляр класса и выполнить любой 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 (этому будет посвящён следующий урок).
Всем спасибо, с ув. Вячеслав.
Скачать полностью проект можно по ссылкам:
Ужас SELECT * FROM table для получения количества записей...
ВідповістиВидалитиSELECT COUNT(DISTINCT column) AS co FROM foo
В нашем случае поле id и так уникально поэтому
SELECT COUNT(id) AS c FROM shop LIMIT 1