手撸一个简易Android数据库框架

一、简述

众所周知,移动端(不管是Android还是iOS)使用的数据库是Sqlite,这种小型的数据库很适合移动端存储大量的数据,使用上也跟mysql基本无差,但官方提供的API在操作性方面真不咋的,你必须掌握一定程度的sql语句,否则将很难驾驭。所以,有很多第三方的数据库框架就开始流行,如:GreenDao、Litepal等。这些ORM数据库框架,可以帮助开发者节省大量编写数据库操作代码的时间,只需对对象进行赋值操作,便能作用到数据库上,方便我们开发更加复杂的业务逻辑。本篇的主题就是做一个简易的数据库框架,使用设计模式、泛型、注解、反射这些高级技巧来实现。

二、数据库常用操作

数据库操作无非就是增删改查(CRUD),而且一般操作数据库表的类称为Dao类,所以可以为这些Dao类抽取一个共同的接口:

public interface IBaseDao<M> {

    Long insert(M entity);

    Integer delete(M where);

    Integer update(M entitiy, M where);

    List<M> query(M where);

    List<M> query(M where, String orderBy);

    List<M> query(M where, String orderBy, Integer page, Integer pageCount);

}

我们要做的数据库框架也是一个ORM框架,表现层不涉及任何sql语句,直接操作的是数据对象,但具体的数据类型在这个接口中并不清楚,所以使用泛型来表示。

三、Dao类工厂

一个程序,一般只有一个数据库,一个数据库中会包含多张表,例如用户表,权限表等,这就意味着,项目中,Dao类会有多个,因为数据库操作无非是CRUD,所以可以确定它们的结构相同,只是具体操作的表与字段不同(即数据类型不同),所以,使用“泛型+工厂”来生产这些Dao类最合适不过。下面先贴出Dao类工厂代码,再一一分析:

public class BaseDaoFactory {

    private static String mDbPath;
    private SQLiteDatabase mDatabase;

    private static class Instance {
        public static BaseDaoFactory INSTANCE = new BaseDaoFactory();
    }

    public static BaseDaoFactory getInstance() {
        return Instance.INSTANCE;
    }

    // 初始化数据库位置
    public static void init(String dbPath) {
        mDbPath = dbPath;
    }

    public BaseDaoFactory() {
        if (TextUtils.isEmpty(mDbPath)) {
            throw new RuntimeException("在使用BaseDaoFactory之前,请调用BaseDaoFactory.init()初始化好数据库路径。");
        }
        // 打开数据库,得到数据库对象
        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDbPath, null);
    }

    public <T extends BaseDao<M>, M> T getDataHelper(Class<T> clazz, Class<M> entity) {
        T baseDao = null;
        try {
            baseDao = clazz.newInstance();
            baseDao.init(mDatabase, entity);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return baseDao;
    }
}

1、数据库位置

使用SQLiteDatabase,可以在任意位置创建或打开一个数据库,这样的好处是:如果,用SQLiteOpenHelper来创建数据库,默认会将数据库文件创建到/data/data/包名/databases目录下,当应用被删除时,数据库也将同应用一起被删除,有时我们会有这样的需求,在用户重装安装App时,可以使用之前的数据库信息,例如:UC浏览器的书签本地恢复功能。

// 初始化数据库位置
public static void init(String dbPath) {
    mDbPath = dbPath;
}

public BaseDaoFactory() {
    if (TextUtils.isEmpty(mDbPath)) {
        throw new RuntimeException("在使用BaseDaoFactory之前,请调用BaseDaoFactory.init()初始化好数据库路径。");
    }
    // 打开数据库,得到数据库对象
    mDatabase = SQLiteDatabase.openOrCreateDatabase(mDbPath, null);
}

本框架可以让开发者自定义数据库的存放位置,因为在构造函数中会使用到该路径对数据库进行创建,所以这里使用静态方法的方式,在Dao工厂实例化之前先对其(mDbPath)进行赋值。这种方式在很多第三方框架的源码中很是常见,一般在自定义的Application中对通过调用框架的init()方法对框架中必需的变量进行赋值。

2、工厂单例

该框架中的Dao类工厂只要一个就够了,所以需要用到单例模式,常见的单例模式有饿汉式和懒汉式,这里选用静态内部类单例模式,原因是什么呢?

1)饿汉式

饿汉式在类加载时就已经初始化好了,不管项目中是否使用,都会占用内存,虽然效率高,但开发中一般不用。

public class BaseDaoFactory {
    private static BaseDaoFactory Instance = new BaseDaoFactory();
    public static BaseDaoFactory getInstance() {
        return Instance;
    }
}

2)懒汉式

懒汉式虽然解决了饿汉式的弊端,实现调用时创建单例,但线程不安全,当然我们可以使用双重检测机制来解决,但这样也降低了效率(至少第一次初始化时需要同步,降低了效率),是开发中最常见的单例实现方式。

public class BaseDaoFactory {
    private static BaseDaoFactory mInstance;
    public static BaseDaoFactory getInstance() {
        if (mInstance == null) {
            synchronized (BaseDaoFactory.class) {
                if (mInstance == null) {
                    mInstance = new BaseDaoFactory();
                }
            }
        }
        return mInstance;
    }
}

3)静态内部类单例

静态内部类单例综合了前面两者的优点,即调用时创建单例,效率高且没有线程安全问题。当在调用getInstance()方法创建工厂单例时,静态内部类Instance才会被加载,同时初始化内部类属性INSTANCE,即初始化外部类BaseDaoFactory对象(Dao工厂),因为该静态内部类只会加载一次,所以该INSTANCE对象也只会被创建一次。

private static class Instance {
    public static BaseDaoFactory INSTANCE = new BaseDaoFactory();
}

public static BaseDaoFactory getInstance() {
    return Instance.INSTANCE;
}

// 单例模式,一般会将构造函数私有化,以保证不会被外界初始化。
private BaseDaoFactory() {
   ...
}

3、生产Dao类

Dao工厂会提供一个public方法来供外界获取需要的Dao类,而外界只需要传入具体Dao类和数据实体类对应的class即可。

public <T extends BaseDao<M>, M> T getDataHelper(Class<T> clazz, Class<M> entity) {
    T baseDao = null;
    try {
        baseDao = clazz.newInstance();
        baseDao.init(mDatabase, entity);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return baseDao;
}

可能这部分代码比较迷的地方就是<T extends BaseDao<M>, M>了(先抛开方法体中的具体实现),其实这只是方法声明泛型的方式而已。我们知道,类上声明泛型很简单,只需要在类后面加上<T>(字母随意)即可,如:

// 类上泛型可以声明多个
public class Person<M,T> {
    ...
}

而方法上声明泛型跟类上声明泛型有些区别——泛型声明的位置不同,方法上的泛型需要在修饰符(public等)与返回值之间声明,如:

public <T> void eat(){
    ...
}

上面的代码不会报错,但一点意义都没有,因为泛型没被使用到,一般方法上声明的泛型可作为方法参数类型和返回值类型,如:

// 同样,方法泛型也可以声明多个
public <T,M> T eat(T t, M m){
    ...
}

可以这样认为,默认泛型表示的是Object(编码时),有时我们需要更加精确泛型的类型,这可以通过extends办到,如:

public <T extends Person> void doSomething(T t){
    ...
}

这时,传入doSomething()的参数必须是Person的子类(Boy或Girl),且在编码时,可以在方法体中使用t调用Person中声明的方法,如eat(),如果不使用extends来指定泛型T的具体类型,那么在编码时,t会被认为是Object类型,也就没法调用eat()这类自定义的方法了。到这里,回头再看上面的代码,应该就不会迷了。

四、数据库操作封装

前面一开始的时候就已经为Dao类抽取了一个共同的接口,规范了Dao类的基本操作,而且,我们不想在具体的Dao类中直接操作数据库,所以,在这两者中间必须有一层来完成数据库操作,并对各操作方法进行封装,它就是BaseDao,以下是该类中可供外界调用的方法:

public abstract class BaseDao<M> implements IBaseDao<M> {
    protected boolean init(SQLiteDatabase database, Class<M> entity) {
        ...
    }
    @Override
    public Long insert(M entity) {
        ...
    }
    @Override
    public Integer delete(M where) {
        ...
    }

    @Override
    public Integer update(M entitiy, M where) {
        ...
    }

    @Override
    public List<M> query(M where) {
        ...
    }

    @Override
    public List<M> query(M where, String orderBy) {
        ...
    }

    @Override
    public List<M> query(M where, String orderBy, Integer page, Integer pageCount) {
        ...
    }
}

在框架设计中,要切记,不需要被外界(表现层)所知的方法或属性请尽量私有化,一来对框架安全,二来避免团队开发中不必要的冲突。因为BaseDao的init()方法只被DaoFactory调用,且两者均在同包下,故使用protected修饰。

1、自定义注解:TbName和TbField

在对BaseDao进行解析前,先来说说两个十分重要的注解——TbName和TbField。几乎市面上所有的ORM数据库框架,都会用到自定义注解来对一个数据实体进行描述,比如User类的类名对应表的表名,有可能是user,也有可能是t_user,那开发者就可以使用框架提供的注解(TbName)来进行自定义表名了,同理,类中的属性名对应表的字段名也是如此,不过对于表的初始化还需要知道表字段的长度,所以表字段注解(TbField)还多了一个length属性。

/**
 * 表名注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TbName {
    String value();
}

/**
 * 表字段注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TbField {
    String value();
    int length();
}

一方面,BaseDao对TbName和TbField的解析是处于程序运行阶段,所以这2个注解必须是运行时可见,即Retention的值必须是RetentionPolicy.RUNTIME。
另一方面,TbName是要注解在类上的,所以其Target的值是ElementType.TYPE;而TbField则是注解在类属性上的,所以其Target的值是ElementType.FIELD。

2、Dao类初始化

在Dao工厂中就已经使用了这个BaseDao的init()方法,来对Dao类进行一些通用的初始化工作,下面就来看看它都初始化了什么:

/**
 * 初始化表操作对象,一般包括:创建表、获取表字段与类字段的映射关系
 */
protected boolean init(SQLiteDatabase database, Class<M> entity) {
    mDatabase = database;
    mEntityClass = entity;
    
    // 往后的操作必须是基于数据库已经打开的前提下
    if (!database.isOpen()) {
        return false;
    }

    // 获取表名
    TbName tbName = entity.getAnnotation(TbName.class);
    mTbName = tbName == null ? entity.getSimpleName() : tbName.value();

    // 获取表映射字段
    if (!genFieldMap()) {
        return false;
    }

    // 创建数据库
    if (!createTable(database)) {
        return false;
    }

    return true;
}

在这个init()方法中,可以看到,注解TbName率先被该框架使用到了,当开发者有使用该注解时,表名以TbName注解中的值为表名,否则以类名作为表名。

注解是“静态的”,在类加载时就已经固定了,也就是说运行时无法修改其值(javassist方式除外),所以,可以利用类的class对象,通过 getAnnotation(注解.class).注解属性() 这种方法也获取注解的属性值。

1)获取表字段与类字段的映射关系

我们知道,类的属性名可能会与表的字段名不同,而BaseDao中的很多后续操作都会跟这两者打交道,所以,在BaseDao的初始化过程中将这两者的关系使用Map进行保存,方便后续的各种操作。

private boolean genFieldMap() {
    mFieldMap = new HashMap<>();
    Field[] fields = mEntityClass.getFields();
    if (fields == null || fields.length == 0) {
        Log.e(TAG, "获取不到类中字段");
        return false;
    }
    for (Field field : fields) {
        field.setAccessible(true);
        TbField tbField = field.getAnnotation(TbField.class);
        mFieldMap.put(tbField == null ? field.getName() : tbField.value(), field);
    }
    return true;
}    

反射中的几个小知识点:

  • Field[] fields = mEntityClass.getFields();// 得到类中的public字段,包括父类。
  • Field[] fields = mEntityClass.getDeclaredFields();// 得到类中声明的字段(不管是public、protected、private),不包括父类。
  • field.setAccessible(true);// 将私有属性或final属性可以被访问

考虑到数据实体类可能会使用继承的方式来拓展父类,即会用到父类的属性值,所以使用getFields()方法,但代价就是实体类中的属性必须是public的。

2)创建表

Dao类的初始化工作也包括了表的创建。一方面,因为不能在每次创建并初始化Dao类时都去重新创建一次表,所以这里就用到了sql语句中的 if not exists 关键字来避免重复创建表的问题。另一方面,其实创建表的sql语句是一种模板,两个不同的表在使用sql创建时,无非就是表名、字段名、字段类型和字段长度不同,而恰好,这些不同的元素可以使用反射+TbField注解来获取,从而实现sql语句的动态拼接,结合上一步得到的表字段与类字段的映射关系(mFieldMap),代码可以这么写:

/**
 * 创建表(可以被子类重写,方便灵活扩展)
 */
protected boolean createTable(SQLiteDatabase database) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, Field> entry : mFieldMap.entrySet()) {
        String columnName = entry.getKey();
        Field field = entry.getValue();
        TbField tbField = field.getAnnotation(TbField.class);
        int length = tbField == null ? 255 : tbField.length();
        String type = "";
        Class<?> fieldType = field.getType();
        if (fieldType == String.class) {
            type = "varchar";
        } else if (fieldType == int.class || fieldType == Integer.class) {
            type = "int";
        } else if (fieldType == double.class || fieldType == Double.class) {
            type = "double";
        } else if (fieldType == float.class || fieldType == Float.class) {
            type = "float";
        }
        if (TextUtils.isEmpty(type)) {
            Log.e(TAG, type.getClass().getName() + "是不支持的字段");
        } else {
            sb.append(columnName + " " + type + "(" + length + "),");
        }
    }
    sb.deleteCharAt(sb.lastIndexOf(","));
    String s = sb.toString();
    if (TextUtils.isEmpty(s)) {
        Log.e(TAG, "获取不到表字段信息");
        return false;
    }
    String sql = "create table if not exists " + mTbName + " (" + s + ") ";
    Log.e(TAG, sql);
    database.execSQL(sql);
    return true;
}

到这里,Dao类的初始化工作就完了,下面进行CRUD的封装。

3、增

我们知道,若使用原生的SQLiteDatabase将数据插入到表中,需要将数据先封装成ContentValues对象,再调用其insert()方法来执行数据插入操作。那么,现在我们拥有了一个数据实体,要做的,就是将这个数据实体转成ContentValues对象,再使用SQLiteDatabase的insert()方法来执行插入,可以说我们就是对SQLiteDatabase进行封装,总结上面的理论,代码分如下三步:

  1. 将对象中的属性转成键值对values。
  2. 将键值对values转成ContentValues对象。
  3. 使用SQLiteDatabase的insert()方法进行数据插入。

结合mFieldMap,实现表数据插入的代码可以这么写:

@Override
public Long insert(M entity) {
    try {
        Map<String, String> values = getValues(entity);
        ContentValues cv = getContentValues(values);
        return mDatabase.insert(mTbName, null, cv);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0L;
}

/**
 * 将对象中的属性转成键值对(列名--值)
 */
private Map<String, String> getValues(M entity) throws IllegalAccessException {
    Map<String, String> result = new HashMap<>();
    for (Map.Entry<String, Field> entry : mFieldMap.entrySet()) {
        Object value = entry.getValue().get(entity);
        result.put(entry.getKey(), value == null ? "" : value.toString());
    }
    return result;
}

/**
 * 将键值对转成ContentValues
 */
private ContentValues getContentValues(Map<String, String> values) {
    ContentValues cv = new ContentValues();
    for (Map.Entry<String, String> val : values.entrySet()) {
        cv.put(val.getKey(), val.getValue());
    }
    return cv;
}

4、删

要实现删除表数据功能,需要使用到SQLiteDatabase的delete()方法,其中whereClause和whereArgs是关键。又因为该框架是一个ORM框架,在表现层需要将删除条件使用数据实体进行封装,而框架内部则是对传入的数据实体进行解析,将对象中属性值不为null的属性拿出来作为删除的条件(这也意味着常见的数据类型不能用了,如int,但可以使用Integer来替换),可分为两步:

  1. 将对象中的属性转成键值对whereMap。
  2. 使用Condition类的构造函数对whereMap中value不为null的键值对取出来拼接。

综上所述,BaseDao的表数据删除实现代码如下:

@Override
public Integer delete(M where) {
    try {
        Map<String, String> whereMap = getValues(where);
        Condition condition = new Condition(whereMap);
        return mDatabase.delete(mTbName, condition.whereClause, condition.whereArgs);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0;
}

class Condition {
    public Condition(Map<String, String> whereMap) {

        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();

        for (Map.Entry<String, String> entry : whereMap.entrySet()) {
            if (!TextUtils.isEmpty(entry.getValue())) {
                sb.append("and " + entry.getKey() + "=? ");
                list.add(entry.getValue());
            }
        }
        this.whereClause = sb.delete(0, 4).toString();
        this.whereArgs = list.toArray(new String[list.size()]);
    }

    String whereClause;
    String[] whereArgs;
}

whereClause是删除条件,是个字符串,需要使用?来作为占位符,多个条件需要使用and关键字连接,如:name=? and password=?

whereArgs则是对whereClause中占位符进行数值替换的字体串数组,如:new String[]{"LQR","123456"}

5、改

通过前面对表数据增和删的代码实现,更新数据部分就比较好理解了,因为SQLiteDatabase的update()方法需要用到的参数有ContentValues对象,whereClause和whereArgs,其实就是将增和删的代码实现相加起来而已,这就不多废话,实现的代码如下:

@Override
public Integer update(M entitiy, M where) {
    try {
        Map<String, String> values = getValues(entitiy);
        ContentValues cv = getContentValues(values);

        Map<String, String> whereMap = getValues(where);
        Condition condition = new Condition(whereMap);

        return mDatabase.update(mTbName, cv, condition.whereClause, condition.whereArgs);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0;
}

6、查

终于到了CRUD的最后一步:数据查询(Retrieve)。可以说这是数据库操作中最重要且使用频率最高的一部分了,同样的,表数据查询还是用到了SQLiteDatabase,使用其query()方法来进行表数据查询,它的参数也比较多,这里就只封装三种查询:

  1. 将符合条件的表数据全部查询出来。
  2. 将符合条件的表数据查询出来,并可以排序。
  3. 将符合条件的表数据查询出来,除了可以排序,还可以分页查询。

需要注意的就是分页查询,因为SQLiteDatabase的第一页是从0开始的,而我希望的是表现层从1开始,所以框架代码中会对其进行自减处理。这三个方法的代码具体实现如下:

@Override
public List<M> query(M where) {
    return query(where, null);
}

@Override
public List<M> query(M where, String orderBy) {
    return query(where, orderBy, null, null);
}

@Override
public List<M> query(M where, String orderBy, Integer page, Integer pageCount) {
    List<M> list = null;
    Cursor cursor = null;
    try {
        String limit = null;
        if (page != null && pageCount != null) {
            int startIndex = --page;
            limit = (startIndex < 0 ? 0 : startIndex) + "," + pageCount;
        }

        if (where != null) {
            Map<String, String> whereMap = getValues(where);
            Condition condition = new Condition(whereMap);
            cursor = mDatabase.query(mTbName, null, condition.whereClause, condition.whereArgs, null, null, orderBy, limit);
        } else {
            cursor = mDatabase.query(mTbName, null, null, null, null, null, orderBy, limit);
        }

        // 将查询出来的表数据转成对象集合
        list = getDataList(cursor);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
            cursor = null;
        }
    }
    return list;
}

/**
 * 通过游标,将表中数据转成对象集合
 */
private List<M> getDataList(Cursor cursor) throws IllegalAccessException, InstantiationException {
    if (cursor != null) {
        List<M> result = new ArrayList<>();
        // 遍历游标,获取表中一行行的数据
        while (cursor.moveToNext()) {

            // 创建对象
            ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();// 获取当前new的对象的 泛型的父类 类型
            Class<M> clazz = (Class<M>) pt.getActualTypeArguments()[0];// 获取第一个类型参数的真实类型
            M item = clazz.newInstance();

            // 遍历表字段,使用游标一个个取值,赋值给新创建的对象。
            Iterator<String> iterator = mFieldMap.keySet().iterator();
            while (iterator.hasNext()) {
                // 找到表字段
                String columnName = iterator.next();
                // 找到表字段对应的类属性
                Field field = mFieldMap.get(columnName);

                // 根据类属性类型,使用游标获取表中的值
                Object val = null;
                Class<?> fieldType = field.getType();
                if (fieldType == String.class) {
                    val = cursor.getString(cursor.getColumnIndex(columnName));
                } else if (fieldType == int.class || fieldType == Integer.class) {
                    val = cursor.getInt(cursor.getColumnIndex(columnName));
                } else if (fieldType == double.class || fieldType == Double.class) {
                    val = cursor.getDouble(cursor.getColumnIndex(columnName));
                } else if (fieldType == float.class || fieldType == Float.class) {
                    val = cursor.getFloat(cursor.getColumnIndex(columnName));
                }

                // 反射给对象属性赋值
                field.set(item, val);
            }
            // 将对象添加到集合中
            result.add(item);
        }
        return result;
    }
    return null;
}

至此,这个简易的数据库框架就写好了,下面来测试一下。

五、测试

Activity的布局很简单,我就不贴了,就是几个简单的按钮而已。

1、测试前准备

1)User

一个简单的数据实体类,没什么好说的,看代码。

@TbName("tb_user")
public class User {

    @TbField(value = "tb_name", length = 30)
    public String username;

    @TbField(value = "tb_password", length = 20)
    public String password;

    @TbField(value = "tb_age", length = 11)
    public Integer age;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "[username:" + this.username + ", password:" + this.getPassword() + ", age:" + this.getAge() + "]";
    }
}

2)UserDao

可以看到这个UserDao中其实没什么代码,但它可以通过重写父类createTable()方法来更灵活的创建表,或自定义一些其它的方法来扩展其父类的功能。

public class UserDao extends BaseDao<User> {
    // @Override
    // protected boolean createTable(SQLiteDatabase database) {
    // database.execSQL("create table if not exists t_user(tb_name varchar(30),tb_password varchar(10))");
    // return super.createTable(database);
    // }
}

2、功能测试

1)初始化

这个框架需要指定一个数据库位置,我们在Activity的onCreate()方法中调用框架的init()方法来指定,建议最好放到自定义的Application中。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_customer_db_frame);
    BaseDaoFactory.init(new File(getFilesDir(), "csdn_lqr.db").getAbsolutePath());
    mUserDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
    mUser = new User("CSDN_LQR", "123456", 10);
}

2)增

往user表中插入一条username为"CSDN_LQR",password为"123456"的数据:

public void insert(View view) {
    Long insert = mUserDao.insert(mUser);
    Toast.makeText(getApplicationContext(), "添加了" + (insert != -1 ? 1 : 0) + "条数据", Toast.LENGTH_SHORT).show();
}

3)删

从user表中删除一条username为"CSDN_LQR"的数据:

public void delete(View view) {
    User where = new User();
    where.setUsername("CSDN_LQR");
    Integer delete = mUserDao.delete(where);
    Toast.makeText(getApplicationContext(), "删除了" + delete + "条数据", Toast.LENGTH_SHORT).show();
}

4)改

将user表中username为"CSDN_LQR"的数据进行修改:

public void update(View view) {
    User user = new User("LQR_CSDN", "654321", 9);

    User where = new User();
    where.setUsername("CSDN_LQR");

    Integer update = mUserDao.update(user, where);
    Toast.makeText(getApplicationContext(), "修改了" + update + "条数据", Toast.LENGTH_SHORT).show();
}

5)查

a. 将符合条件的表数据全部查询出来

将user表中所有username为"CSDN_LQR"的数据全部查询出来:

public void query1(View view) {
    User where = new User();
    where.setUsername("CSDN_LQR");

    List<User> list = mUserDao.query(where);
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "条数据", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}

b. 将符合条件的表数据查询出来,并排序

将user表中的数据按age的正反序分别查询出来:

public void query2(View view) {
    List<User> list = mUserDao.query(null, "tb_age asc");
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "条数据", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}

public void query3(View view) {
    List<User> list = mUserDao.query(null, "tb_age desc");
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "条数据", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}

c. 将符合条件的表数据查询出来,并分页。

只查询user表中的前2条数据:

public void query4(View view) {
    User where = new User();

    List<User> list = mUserDao.query(where, null, 1, 2);
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "条数据", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}

大成功,撒花。

最后贴下Demo地址:

https://github.com/GitLqr/SimpleDbFrame

欢迎关注微信公众号:全栈行动
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容