自己动手写Android数据库框架

scorpio300 8年前

来自: http://finalshares.com/read-6711

自己动手写Android数据库框架

相信不少开发者跟我一样,每次都很烦恼自己写数据库,而且那些数据库语句也经常记不住。当然网上也有很多很好的数据库框架,你可以直接拿来用,但是 很多时候我们的项目,特别是一个小型的Andrond应用原本用到的数据库结构比较简单,没必要去用那些有点臃肿的框架。当然,即使你用那些框架,当你遇到问题时,你是否也得去修改它?你要修改别人的框架必须的读懂他人的设计代码。所以不管从那个角度出发,你都得掌握简单的数据库操作。那么这篇博客就从简单的数据库操作来学习Android数据库相关知识点,然后一步一步去搭建自己的简单型数据库框架,以后就再也不用担心害怕去写数据库了,直接拿自己的数据库框架用就好了。

框架功能

  1. public long insert(Object obj);插入数据
  2. public List findAll(Class clazz);查询所有数据
  3. public List findByArgs(Class clazz, String select, String[] selectArgs) ;根据指定条件查询满足条件数据
  4. public T findById(Class clazz, int id);根据id查询一条记录
  5. 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列怎么办?还继续按照上面的方法写创建表语句么?你就不嫌繁琐么?而且容易粗错。那么有没有超级简单的方法一步完成表语句的创建呢?你细想:存放在数据库中表的这些字段无非就是一个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 : "";      }  }

如此一来,用户创建数据库表就变的很简单了,传入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));            ..............      }

是不是很简单!!!领导再也不用担心我不会创建数据库了。

数据库操作–插入

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。是不是很简单呢?它的实现如下:

   /**       * 删除记录一条记录       *       * @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的数据。用法很简单,它的实现如下:

 /**       * 更新一条记录       *       * @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

</div>