前言
相信不少開發人員跟我一樣,每次都非常煩惱自己寫數據庫,並且那些數據庫語句也經常記不住。當然網上也有非常多非常好的數據庫框架,你能夠直接拿來用,可是 非常多時候我們的項目。特別是一個小型的Andrond應用原本用到的數據庫結構比較簡單,不是必需去用那些有點臃腫的框架。當然,即使你用那些框架。當你遇到問題時,你是否也得去改動它?你要改動別人的框架必須的讀懂他人的設計代碼。所以無論從那個角度出發,你都得掌握簡單的數據庫操作。那么這篇博客就從簡單的數據庫操作來學習Android數據庫相關知識點。然后一步一步去搭建自己的簡單型數據庫框架,以后就再也不用操心害怕去寫數據庫了,直接拿自己的數據庫框架用就好了。
框架功能
- public long insert(Object obj);插入數據
- public List findAll(Class clazz);查詢全部數據
- public List findByArgs(Class clazz, String select, String[] selectArgs) 。依據指定條件查詢滿足條件數據
- public T findById(Class clazz, int id);依據id查詢一條記錄
- public void deleteById(Class
創建數據庫
Android系統中已經集成了Sqlite數據庫,我們直接使用它就好了,同一時候Android系統提供了一個數據庫幫助類SQLiteOpenHelper,該類是一個抽象類。所以得寫一個類來繼承它實現里面的方法。代碼例如以下:
MySQLiteHelper類
public class MySQLiteHelper extends SQLiteOpenHelper {
public MySQLiteHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
當數據庫創建時系統會調用當中的 onCreate方法,那么我們就能夠來實現 onCreate 方法來創建數據庫表。假設我們要創建一張 Person表,表中有 id,name,age,flag字段。那么代碼例如以下:
public class MySQLiteHelper extends SQLiteOpenHelper {
public static final String CREATE_TABLE = "create table Person ("
+ "id integer primary key autoincrement, "
+ "name text, "
+ "age integer, "
+ "flag boolean)";
public MySQLiteHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
...
}
由此我們的數據庫幫助類就完畢了,接下來是這么使用的:
private static final String DB_NAME = "demo.db";
private static final int DB_VERSION = 1;
public void oepnDB(){
MySQLiteHelper helper = new MySQLiteHelper(context, DB_NAME, null, DB_VERSION);
SQLiteDatabase db = helper.getWritableDatabase();
}
有以上代碼就已經完畢了一個數據庫創建以及一張表的創建。是不不是感覺不是非常難呢?這么看起來的確不是非常難,可是我的也不得不每次去繼承SQLiteOpenHelper類來實現里面的方法。關鍵是每次都要去寫創建表語句
public static final String CREATE_TABLE = "create table Person (" + "id integer primary key autoincrement, " + "name text, " + "age integer, " + "flag boolean)";
這里表的字段僅僅有4個,假設有一天你遇到表里的字段有10列怎么辦?還繼續依照上面的方法寫創建表語句么?你就不嫌繁瑣么?並且easy粗錯。那么有沒有超級簡單的方法一步完畢表語句的創建呢?你細想:存放在數據庫中表的這些字段無非就是一個Person類中的全部成員變量,這么一來能否夠僅僅通過Person類型直接創建表語句呢?答案是肯定的。
我們通過java 的反射機制來一步一勞永逸的實現建表操作。
代碼例如以下:
/** * 得到建表語句 * * @param clazz 指定類 * @return sql語句 */
private String getCreateTableSql(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
//將類名作為表名
String tabName = Utils.getTableName(clazz);
sb.append("create table ").append(tabName).append(" (id INTEGER PRIMARY KEY AUTOINCREMENT, ");
//得到類中全部屬性對象數組
Field[] fields = clazz.getDeclaredFields();
for (Field fd : fields) {
String fieldName = fd.getName();
String fieldType = fd.getType().getName();
if (fieldName.equalsIgnoreCase("_id") || fieldName.equalsIgnoreCase("id")) {
continue;
} else {
sb.append(fieldName).append(Utils.getColumnType(fieldType)).append(", ");
}
}
int len = sb.length();
sb.replace(len - 2, len, ")");
Log.d(TAG, "the result is " + sb.toString());
return sb.toString();
}
工具類代碼例如以下:
package com.xjp.databasedemo;
import android.text.TextUtils;
import java.util.Locale;
/** * Created by xjp on 2016/1/23. */
public class DBUtils {
//得到每一列字段的數據類型
public static String getColumnType(String type) {
String value = null;
if (type.contains("String")) {
value = " text ";
} else if (type.contains("int")) {
value = " integer ";
} else if (type.contains("boolean")) {
value = " boolean ";
} else if (type.contains("float")) {
value = " float ";
} else if (type.contains("double")) {
value = " double ";
} else if (type.contains("char")) {
value = " varchar ";
} else if (type.contains("long")) {
value = " long ";
}
return value;
}
//得到表名
public static String getTableName(Class<?
> clazz){ return clazz.getSimpleName(); } public static String capitalize(String string) { if (!TextUtils.isEmpty(string)) { return string.substring(0, 1).toUpperCase(Locale.US) + string.substring(1); } return string == null ? null : ""; } }
如此一來。用戶創建數據庫表就變的非常easy了,傳入Person類的類型(Person.class)作為參數,那么代碼就幫你創建出了一張名字為Person的表。使用代碼例如以下:
class MySqLiteHelper extends SQLiteOpenHelper {
..................
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
}
/** * 依據制定類名創建表 */
private void createTable(SQLiteDatabase db) {
db.execSQL(getCreateTableSql(Person.class));
..............
}
是不是非常easy!
。。領導再也不用操心我不會創建數據庫了。
數據庫操作–插入
android提供的數據庫插入操作
對數據庫插入操作 SQLite提供了例如以下方法
public long insert(String table, String nullColumnHack, ContentValues values)
能夠看到。第一個參數是table 表示表名,第二個參數通經常使用不到,傳入null就可以,第三個參數將數據以 ContentValues鍵值對的形式存儲。比方我們在數據庫中插入一條人Person的信息代碼例如以下:
public void insert(Person person){
ContentValues values = new ContentValues();
values.put("name",person.getName());
values.put("age",person.getAge());
values.put("flag",person.getFlag());
db.insert("Person",null,values);
}
當中ContentValues是以鍵值對的形式存儲數據,上面代碼中的key 分別相應數據庫中的每一列的字段。vaule分別相應着該列的值。你是否發現Person類中有幾個屬性就得寫多少行values.put(key。value);增加它有10個字段須要保存到數據庫中。你是否認為這樣非常麻煩呢?認為麻煩就對了,接下來我們利用反射來一步完畢以上數據庫插入操作。
數據庫插入框架
數據庫插入操作框架能夠減輕你寫代碼量,讓你一步完畢數據庫插入操作而無須關注其內部繁瑣的操作。相同利用java反射來實現以上效果。
代碼例如以下:
/** * 插入一條數據 * * @param obj * @return 返回-1代表插入數據庫失敗。否則成功 * @throws IllegalAccessException */
public long insert(Object obj) {
Class<?> modeClass = obj.getClass(); Field[] fields = modeClass.getDeclaredFields(); ContentValues values = new ContentValues(); for (Field fd : fields) { fd.setAccessible(true); String fieldName = fd.getName(); //剔除主鍵id值得保存,由於框架默認設置id為主鍵自己主動增長 if (fieldName.equalsIgnoreCase("id") || fieldName.equalsIgnoreCase("_id")) { continue; } putValues(values, fd, obj); } return db.insert(DBUtils.getTableName(modeClass), null, values); } ............ /** * put value to ContentValues for Database * * @param values ContentValues object * @param fd the Field * @param obj the value */ private void putValues(ContentValues values, Field fd, Object obj) { Class<?
> clazz = values.getClass(); try { Object[] parameters = new Object[]{fd.getName(), fd.get(obj)}; Class<?
>[] parameterTypes = getParameterTypes(fd, fd.get(obj), parameters); Method method = clazz.getDeclaredMethod("put", parameterTypes); method.setAccessible(true); method.invoke(values, parameters); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
有以上框架之后。我們如今來向數據庫插入一條數據代碼例如以下:
Person person = new Person("Tom",18,false);
DBManager.insert(person);
哇。如此簡單。一行代碼解決繁瑣的插入操作。我們僅僅須要傳入Person對象的實例作為參數就可以完畢數據庫插入操作。再也不用去構建什么ContentVaules鍵值對了。
數據庫操作–查詢
android提供的數據庫查詢
android 的sqlite數據庫提供的查詢語句有rawQuery()方法。該方法的定義例如以下:
public Cursor rawQuery(String sql, String[] selectionArgs)
當中第一個參數是sql字符串,第二個參數是用於替換SQL語句中占位符(?
)的字符串數組。返回結果存放在Cursor對象當中,我們僅僅要循環一一取出數據就可以。當然我們平時不怎么用這種方法,由於須要記住非常多數據庫查詢語句的規則等。Android給開發人員封裝了另外一個數據庫查詢方法。即SQLiteDatabase中的query()方法。
該方法的定義例如以下:
public Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy)
當中,
第一個參數是須要查詢數據的表名稱。
第二個參數指查詢表中的那幾列字段,假設不指定則默認查詢全部列。
第三個參數是sql語句,表示查詢條件。
第四個參數是用於替換第三個參數sql語句中的占位符(?)數組,假設第三,四個參數不指定則默認查詢全部行;
第五個參數用於指定須要去group by的列,不指定則表示不正確查詢結果進行group by操作。
第六個參數用於對group by之后的數據進行進一步的過濾。不指定則表示不進行過濾。
第七個參數用於指定查詢結果的排序方式,不指定則表示使用默認的排序方式。
query()方法的參數是不是非常多,一般人都非常難記住這些參數的意思,在用的時候就非常不方便,比方你要查詢數據庫中 age=18的人。你的代碼得這么寫:
Cursor cursor = db.query("Person", null, "age = ?
", new String[]{"18"}, null, null, null);
第三個參數是查詢條件,去約束查詢結果 age = 18。所以 第三個參數是“age= ?”。第四個參數用於替換第三個參數的占位符(?)。因此是String的數組。
查詢的結果保存在Cursor中,為了拿到查詢結果,我們不得不去變量里Cursor一一取出當中的數據並保存。
代碼例如以下:
List<Person> list = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
Person person = new Person();
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String age = cursor.getString(cursor.getColumnIndex("age"));
boolean flag = cursor.getInt(cursor.getColumnIndex("flag")) == 1 ? true : false;
person.setId(id);
person.setName(name);
person.setAge(age);
person.setFlag(flag);
list.add(person);
} while (cursor.moveToNext());
}
為了取得Cursor中的查詢結果。我們寫了如此多的繁瑣的代碼。假設此時有一個新的Student類,那么你是否又要去改動這個查詢方法呢?如此看來該查詢方法和取得結果是不是沒有通用性。非常不方便使用。
對於討厭敲反復代碼的程序猿來說這樣非常麻煩。用的不爽,那么有沒有一種方法直接將查詢結果轉換成我須要的類的集合呢?這里我們又要用到自己寫的查詢框架了,利用該框架一行代碼就可以搞定全部。
數據庫查詢框架
1.查詢數據庫中全部數據
/** * 查詢數據庫中全部的數據 * * @param clazz * @param <T> 以 List的形式返回數據庫中全部數據 * @return 返回list集合 * @throws IllegalAccessException * @throws InstantiationException * @throws NoSuchMethodException * @throws InvocationTargetException */
public <T> List<T> findAll(Class<T> clazz) {
Cursor cursor = db.query(clazz.getSimpleName(), null, null, null, null, null, null);
return getEntity(cursor, clazz);
}
.....................
/** * 從數據庫得到實體類 * * @param cursor * @param clazz * @param <T> * @return */
private <T> List<T> getEntity(Cursor cursor, Class<T> clazz) {
List<T> list = new ArrayList<>();
try {
if (cursor != null && cursor.moveToFirst()) {
do {
Field[] fields = clazz.getDeclaredFields();
T modeClass = clazz.newInstance();
for (Field field : fields) {
Class<?
> cursorClass = cursor.getClass(); String columnMethodName = getColumnMethodName(field.getType()); Method cursorMethod = cursorClass.getMethod(columnMethodName, int.class); Object value = cursorMethod.invoke(cursor, cursor.getColumnIndex(field.getName())); if (field.getType() == boolean.class || field.getType() == Boolean.class) { if ("0".equals(String.valueOf(value))) { value = false; } else if ("1".equals(String.valueOf(value))) { value = true; } } else if (field.getType() == char.class || field.getType() == Character.class) { value = ((String) value).charAt(0); } else if (field.getType() == Date.class) { long date = (Long) value; if (date <= 0) { value = null; } else { value = new Date(date); } } String methodName = makeSetterMethodName(field); Method method = clazz.getDeclaredMethod(methodName, field.getType()); method.invoke(modeClass, value); } list.add(modeClass); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } return list; }
查詢全部數據並且自己主動保存在List中返回,無須用戶去將Cursor解析成對象封裝。簡單易用。自須要一個方法一個參數就可以。調用代碼例如以下:
List<Person> list = dbManager.findAll(Person.class);
超級簡單啊!
2.查詢指定條件的數據
/** * 依據指定條件返回滿足條件的記錄 * * @param clazz 類 * @param select 條件語句 :("id>?") * @param selectArgs 條件(new String[]{"0"}) 查詢id=0的記錄 * @param <T> 類型 * @return 返回滿足條件的list集合 */
public <T> List<T> findByArgs(Class<T> clazz, String select, String[] selectArgs) {
Cursor cursor = db.query(clazz.getSimpleName(), null, select, selectArgs, null, null, null);
return getEntity(cursor, clazz);
}
3.依據指定id查詢一條數據
/** * 通過id查找制定數據 * * @param clazz 指定類 * @param id 條件id * @param <T> 類型 * @return 返回滿足條件的對象 */
public <T> T findById(Class<T> clazz, int id) {
Cursor cursor = db.query(clazz.getSimpleName(), null, "id=" + id, null, null, null, null);
List<T> list = getEntity(cursor, clazz);
return list.get(0);
}
用戶代碼調用例如以下:
Person p = dbManager.findById(Person.class, 1);
查詢id=1的數據,第一個參數為Person類型,第二個參數為id值,查詢結果直接保存在Person對象p里。
以上就是自己封裝的數據庫查詢操作。簡單易用,無須記住quary()方法中的那么多參數。也無須自己去一個個解析Cursor數據並保存。該方法一步到位,直接返回Person類型的list集合。
凝視:當中用到的一些方法我臨時沒有貼出來,文章最后我會把樣例和代碼都貼出來。
數據庫刪除操作
android提供的刪除
android系統提供了sqlite數據庫刪除方法 delete(),其定義例如以下:
public int delete(String table, String whereClause, String[] whereArgs)
當中,第一個參數表示表名,第二個參數是條件SQL語句,第三個參數是替換第二個參數中的占位符(?)。
假如我要刪除Person表中的age=18的數據,則代碼調用例如以下:
db.delete("Person","age = ?",new String[]{"18"});
數據庫刪除框架
刪除這一塊比較簡單,我直接貼出代碼來
/** * 刪除記錄一條記錄 * * @param clazz 須要刪除的類名 * @param id 須要刪除的 id索引 */
public void deleteById(Class<?> clazz, long id) {
db.delete(DBUtils.getTableName(clazz), "id=" + id, null);
}
用戶調用例如以下:
dbManager.deleteById(Person.class, 1);
第一個 參數是Person類的類型,第二個參數是被刪除數據的id。是不是非常easy呢?它的實現例如以下:
/** * 刪除記錄一條記錄 * * @param clazz 須要刪除的類名 * @param id 須要刪除的 id索引 */
public void deleteById(Class<?
> clazz, long id) { db.delete(DBUtils.getTableName(clazz), "id=" + id, null); }
數據庫更新操作
android提供的更新操作
在android的sqlite中提供了update()方法來更新數據操作,其定義例如以下:
public int update(String table, ContentValues values, String whereClause, String[] whereArgs)
update()方法接收四個參數。第一個參數是表名。第二個參數是一個封裝了待改動數據的ContentValues對象,第三和第四個參數用於指定改動哪些行,相應了SQL語句中的where部分。
比方我要改動id=1的Person人的年齡age改成20。那么代碼實現例如以下:
ContentValues values = new ContentValues();
values.put("age",20);
db.update("Person",values,"id = ?",new String[]{"1"});
該方法也算比較簡單,那么我們來看看自己寫的數據庫框架是怎么實現的呢?
數據庫框架更新操作
ContentValues values = new ContentValues();
values.put("age", 34);
dbManager.updateById(Person.class, values, 1);
第一個參數為Person類的類型。第二個參數為須要更新的vaules,第三個參數是條件,更新id為1的數據。使用方法非常easy,它的實現例如以下:
/** * 更新一條記錄 * * @param clazz 類 * @param values 更新對象 * @param id 更新id索引 */
public void updateById(Class<?
> clazz, ContentValues values, long id) { db.update(clazz.getSimpleName(), values, "id=" + id, null); }
總結
自此。數據庫的基本操作都羅列出來了,也說明了Android提供的sqlite數據庫在平時開發中的一些繁瑣的地方。所以自己總結提取了一個簡單型的數據庫操作框架,僅僅是比較簡單的操作。假設你有數據量大的操作,請出門左轉利用其它多功能成熟穩定的數據庫開源框架。
該框架僅僅適合數據量小。不存在表與表之間的相應關系。能夠將查詢結果直接轉換成對象的輕量級框架。
源代碼以及演示樣例地址:DataBaseDemo