自己动手写Android数据库框架
2017-07-15 17:20
344 查看
前言
相信不少开发人员跟我一样,每次都非常烦恼自己写数据库,并且那些数据库语句也经常记不住。当然网上也有非常多非常好的数据库框架,你能够直接拿来用,可是 非常多时候我们的项目。特别是一个小型的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
相关文章推荐
- 自己动手写Android数据库框架
- 自己动手写Android框架-数据库框架
- 自己动手搭建数据库框架
- 自己动手写 android 数据库(2)
- Android 最好的数据库框架 ORMLite的分析 (附时序图和自己写的DEMO)
- 自己动手写个Android数据库orm框架,支持关联关系,数据懒加载
- Android自己动手做查找控件、绑定监听的注解框架
- 自己动手写Android插件化框架,让老板对你刮目相看
- 自己动手写Android插件化框架,让老板对你刮目相看
- Android自己动手打造XML解析框架
- 自己动手写一个轻量级的Android网络请求框架
- 自己动手写一个轻量级的Android网络请求框架
- 自己比较喜欢用的安卓数据库框架ActiveAndroid
- Android自己动手打造XML解析框架
- 自己整理的一个Android数据库工具框架
- 自己动手写DB数据库框架(增)
- 继文章‘’ 自己动手写一个轻量级的Android网络请求框架‘’后续------增加缓存功能
- 继文章‘’ 自己动手写一个轻量级的Android网络请求框架‘’补充------增加进度回调
- 扔掉log4j、log4j2,自己动手实现一个多功能日志记录框架,包含文件,数据库日志写入,实测5W+/秒日志文件写入,2W+/秒数据库日志写入,虽然它现在还没有logback那么强大
- 自己动手写 android 数据库(1)