自己动手写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列怎么办?还继续依照上面的方法写创建表语句么?你就不嫌繁琐么?并且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

时间: 2024-08-07 12:04:22

自己动手写Android数据库框架的相关文章

Android 数据库框架OrmLite的使用(一)

在这里记录下最基本的用法,官网上可了解相关的介绍. 1.下载OrmLite jar 在下载android的:ormlite-android-4.48.jar和ormlite-core-4.48.jar,放在你项目的libs目录下. 2.编写Bean类 package com.example.ormlite.bean; import java.util.Collection; import com.j256.ormlite.field.DatabaseField; import com.j256.

Android数据库框架——ORMLite轻量级的对象关系映射(ORM)Java包

Android数据库框架--ORMLite轻量级的对象关系映射(ORM)Java包 事实上,我想写数据库的念头已经很久了,在之前写了一个答题系统的小项目那只是初步的带了一下数据库,数据库是比较强大的,不是我们三言两语就能解决的,我一直想抽个时间自己再过一遍Sqlite和JDBC的,但是,大家也知道,琐事比较多,我们来说一下数据库的框架吧,事实上市面上,我觉得还可以的数据库框架有两个,ORMLite和GreenDao,我以前做的运动类的应用上,就需要存储大量的运动数据,我们最开始是在本地写数据库的

自己动手写PHP MVC框架

来自:yuansir-web.com / [email protected] 代码下载: https://github.com/yuansir/tiny-php-framework PHP的框架众多,对于哪个框架最好,哪个框架最烂,是否应该用框架,对于这些争论在论坛里面都有人争论,这里不做评价, 个人觉得根据自己需求,选中最佳最适合自己MVC框架,并在开发中能够体现出敏捷开发的效果就OK了,作为一个PHPer要提高自己的对PHP和MVC的框架的认识,所以自己写一个MVC框架是很有必要的, 即使不

Android 数据库框架ormlite 使用精要

Android 数据库框架ormlite 使用精要 前言 本篇博客记录一下笔者在实际开发中使用到的一个数据库框架,这个可以让我们快速实现数据库操作,避免频繁手写sql,提高我们的开发效率,减少出错的机率. ormlite是什么? 首先可以去它的官网看看www.ormlite.com,它的英文全称是Object Relational Mapping,意思是对象关系映射:如果接触过Java EE开发的,一定知道Java Web开发就有一个类似的数据库映射框架--Hibernate.简单来说,就是我们

Android数据库框架——GreenDao轻量级的对象关系映射框架,永久告别sqlite

Android数据库框架--GreenDao轻量级的对象关系映射框架,永久告别sqlite 前不久,我在写了ORMLite这个框架的博文 Android数据库框架--ORMLite轻量级的对象关系映射(ORM)Java包 但是对于我个人来说,我可能更加倾向于用GreenDao,所以今天也为大家带来了GreenDao的详细博客,希望大家喜欢,之前也详细介绍了ORM的思想,如果不明白可以先看一下前面的博文,这里就不多赘述了,我们新建一个工程 一.相关介绍 官方网站 : http://greendao

教你写Android ImageLoader框架之初始配置与请求调度

## 前言 在教你写Android ImageLoader框架之基本架构中我们对SimpleImageLoader框架进行了基本的介绍,今天我们就从源码的角度来剖析ImageLoader的设计与实现.   在我们使用ImageLoader前都会通过一个配置类来设置一些基本的东西,比如加载中的图片.加载失败的图片.缓存策略等等,SimpleImageLoader的设计也是如此.配置类这个比较简单,我们直接看源码吧. ImageLoaderConfig配置 /** * ImageLoader配置类,

教你写Android ImageLoader框架之图片缓存 (完结篇)

在教你写Android ImageLoader框架系列博文中,我们从基本架构到具体实现已经更新了大部分的内容.今天,我们来讲最后一个关键点,即图片的缓存.为了用户体验,通常情况下我们都会将已经下载的图片缓存起来,一般来说内存和本地都会有图片缓存.那既然是框架,必然需要有很好的定制性,这让我们又自然而然的想到了抽象.下面我们就一起来看看缓存的实现吧. 缓存接口 在教你写Android ImageLoader框架之图片加载与加载策略我们聊到了Loader,然后阐述了AbsLoader的基本逻辑,其中

教你写Android ImageLoader框架之图片加载与加载策略

在教你写Android ImageLoader框架之初始配置与请求调度中,我们已经讲述了ImageLoader的请求配置与调度相关的设计与实现.今天我们就来深入了解图片的具体加载过程以及加载的策略(包括按顺序加载和逆序加载) ,在这其中我会分享我的一些设计决策,也欢迎大家给我提建议. 图片的加载 Loader与LoaderManager的实现 在上一篇文章教你写Android ImageLoader框架之初始配置与请求调度中,我们聊到了Loader与LoaderManager. ImageLoa

教你写Android网络框架之Http请求的分发与执行

前言 在<教你写Android网络框架>专栏的前两篇博客中,我们已经介绍了SimpleNet框架的基本结构,以及Request.Response.请求队列的实现,以及为什么要这么设计,这么设计的考虑是什么.前两篇博客中已经介绍了各个角色,今天我们就来剖析另外几个特别重要的角色,即NetworkExecutor.HttpStack以及ResponseDelivery,它们分别对应的功能是网络请求线程.Http执行器.Response分发,这三者是执行http请求和处理Response的核心. 我