打造android ORM框架opendroid(六)——级联查询

在上一篇博客《打造android ORM框架opendroid(五)——数据更新的实现》  我们介绍了opendroid数据更新的流程,也就在上次,我们OpenDroid类中的所有操作都介绍完了, 那查询操作呢?不是在OpenDroid中?查询操作是在OpenDroid中,不过是以内部类的形式呈现的。

还是来看看如果使用opendroid查询数据吧。

OpenDroid.query.find(Student.class)
OpenDroid.query.columns("stuName").where("_id>?", "1").limit(1, 4).order("_id DESC").find(Student.class);  

这是opendroid提供的查询方法,也是最常见的级联查询,了解其他ORM框架的朋友们肯定对此不陌生吧,但是,看第二段代码,我们比传统的级联操作多了一个方法columns(),该方法是设置要查询的字段。

好了, 进入我们今天的重点,通过上面代码,我们发现是通过调用了OpenDroid的一个静态字段query的各个方法去组合查询的。我们来看看query。

public static Query query = new Query();

在OpenDroid中这个Query类型的字段,并且在定义的时候就去实例化了。我们再来看看Query这个内部类。

 /**
 * 查询
 * @author qibin
 */
public static class Query {
    private String[] mCocumns = null;  // 要查询的字段
    private String mWhere = null;   // 查询的条件
    private String[] mWhereArgs = null; // 查询的条件的参数
    private String mOrder = null; // order语句
    private String mLimit; // limit语句  

    /**
     * 设置查询的字段
     * @param columns 要查询的字段
     * @return Query对象
     */
    public Query columns(String... columns) {
        mCocumns = columns;
        return this;
    }  

    /**
     * 设置查询的where条件
     * @param where where条件
     * @param whereArgs where参数
     * @return Query对象
     */
    public Query where(String where, String... whereArgs) {
        mWhere = where;
        mWhereArgs = whereArgs;
        return this;
    }  

    /**
     * 设置查询的order
     * @param order order语句
     * @return Query对象
     */
    public Query order(String order) {
        mOrder = order;
        return this;
    }  

    /**
     * 设置查询的limit
     * @param limit limit语句
     * @return Query对象
     */
    public Query limit(int... limit) {
        StringBuilder builder = new StringBuilder();
        builder.append(limit[0]);  

        if(limit.length == 2) {
            builder.append(",").append(limit[1]);
        }  

        mLimit = builder.toString();  

        return this;
    }  

    /**
     * 查询
     * @param klass 映射的bean
     * @return 查询结果
     */
    public <T extends OpenDroid> List<T> find(Class<T> klass) {
        return CRUD.query(klass, mCocumns, mWhere, mWhereArgs,
                mOrder, mLimit, sSqliteDatabase);
    }  

    /**
     * 根据id查询数据
     * @param klass klass 映射的bean
     * @param id 要查询数据的id
     * @return  查询结果
     */
    public <T extends OpenDroid> T find(Class<T> klass, int id) {
        List<T> result = CRUD.query(klass, mCocumns, "_id=?",
                new String[] { String.valueOf(id) }, null, "1",
                sSqliteDatabase);
        return result.size() > 0 ? result.get(0) : null;
    }  

    public <T extends OpenDroid> List<T> find(Class<T> klass, int... ids) {
        StringBuilder builder = new StringBuilder("_id in (");
        String[] whereArgs = new String[ids.length];
        buildIn(builder, whereArgs, ids);  

        return CRUD.query(klass, mCocumns, builder.toString(), whereArgs,
                mOrder, mLimit, sSqliteDatabase);
    }  

    /**
     * 使用sql语句查询
     * @param sql sql语句
     * @param selectionArgs 预编译参数
     * @return
     */
    public Cursor queryBySql(String sql, String[] selectionArgs) {
    	return sSqliteDatabase.rawQuery(sql, selectionArgs);
    }
}

这个内部类中,上来咔咔咔列出了5个字段,注释已经说的很清楚了,其实就是组合查询语句用的。

往下看,我们看到了熟悉的方法,这些都是在上面介绍的时候用到的,并且一些方法的返回值都是this,大家应该很清楚,我们要级联操作嘛,就必须要返回当前对象。

好吧,第一个方法

/**
 * 设置查询的字段
 * @param columns 要查询的字段
 * @return Query对象
 */
public Query columns(String... columns) {
    mCocumns = columns;
    return this;
}  

很简单,就是把要查询的字段保存起来,然后返回当前对象。

/**
 * 设置查询的where条件
 * @param where where条件
 * @param whereArgs where参数
 * @return Query对象
 */
public Query where(String where, String... whereArgs) {
    mWhere = where;
    mWhereArgs = whereArgs;
    return this;
} 

where方法也很简单,保存了where条件和where条件的参数。

/**
 * 设置查询的order
 * @param order order语句
 * @return Query对象
 */
public Query order(String order) {
    mOrder = order;
    return this;
} 

好吧,一样的逻辑(哪里有逻辑!!!)

/**
 * 设置查询的limit
 * @param limit limit语句
 * @return Query对象
 */
public Query limit(int... limit) {
    StringBuilder builder = new StringBuilder();
    builder.append(limit[0]);  

    if(limit.length == 2) {
        builder.append(",").append(limit[1]);
    }  

    mLimit = builder.toString();  

    return this;
} 

设置limit, 这个方法还算长一点,但是一点内容也没有,就是组合一个limit语句,不过这里做了一个数组长度的判断,主要是为了适配limit 1和limit 1,2这两种方式。

/**
 * 查询
 * @param klass 映射的bean
 * @return 查询结果
 */
public <T extends OpenDroid> List<T> find(Class<T> klass) {
    return CRUD.query(klass, mCocumns, mWhere, mWhereArgs, mOrder, mLimit, sSqliteDatabase);
}

这个查询方法直接调用了CRUD.query并返回它的返回值,相信大家看到CRUD肯定很熟悉了,我们定位到CRUD中query这个方法看看吧。

/**
 * 查询数据
 * @param klass 要映射的类
 * @param columns 查询的字段, null 取所有
 * @param where where条件, null 忽略条件
 * @param whereArgs where的参数, null无参数
 * @param order order语句, null忽略order by
 * @param limit limit语句, null忽略limit
 * @param db    数据库句柄
 * @return 查询后的数据
 */
protected static <T extends OpenDroid> List<T> query(Class<T> klass, String[] columns,
        String where, String[] whereArgs, String order, String limit, SQLiteDatabase db) {
    List<T> resultList = new ArrayList<T>(); // 存放结果
    String tableName = klass.getSimpleName(); // 获取类名(表名)
    Cursor cursor = null;  

    try {
        cursor = db.query(tableName, columns, where, whereArgs, null, null, order, limit);
        foreachCursor(klass, cursor, resultList);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if(cursor != null) {
            cursor.close();
        }
    }  

    return resultList;
}

参数有点多,那么我们就从参数说起。

第一个参数是一个Class,应该很清晰了,我们要根据它来获取要查询的表明,接下来的一系列就是要查询的字段,条件,order语句,limit语句,最后一个是我们操作的数据库句柄。

继续看代码,首先new类一个ArrayList,并且获取了要操作的表名,接下来在try中,直接调用了android原生api的query返回一个Cursor对象,这就是我们查询的结果。接下来调用foreachCursor方法,将结果遍历到刚开new的那个ArrayList中,查询操作完毕!

那么我们来看看foreachCursor方法。

/**
 * 遍历数据库游标
 * @param klass 要映射的类
 * @param cursor 要遍历的游标
 * @param resultList 存放返回的结果
 * @throws InstantiationException
 * @throws IllegalAccessException
 */
private static <T extends OpenDroid> void foreachCursor(Class<T> klass,
        Cursor cursor, List<T> resultList) throws InstantiationException,
        IllegalAccessException {
    T t; // 反射出的实例
    String columnName; // 数据库字段名
    String methodName; // 方法名
    Method m; // 反射出的方法  

    for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
        t = klass.newInstance();  // 通过反射进行实例化
        for(int i=0;i<cursor.getColumnCount();i++) {
            columnName = cursor.getColumnName(i); // 获取数据库字段名
            try {
                switch (cursor.getType(i)) {
                case Cursor.FIELD_TYPE_INTEGER:
                    // 如果字段名是_id的话, 对应的方法是setId
                    methodName = columnName.equals("_id") ? "setId" :
                        getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, int.class); // 反射出方法
                    m.invoke(t, cursor.getInt(i)); // 执行方法
                    break;
                case Cursor.FIELD_TYPE_FLOAT:
                    methodName = getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, float.class);
                    m.invoke(t, cursor.getFloat(i));
                    break;
                default:
                    methodName = getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, String.class);
                    m.invoke(t, cursor.getString(i));
                    break;
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        resultList.add(t);
    }
}

又是一个巨长的...

我们来一点点分析吧。首先看参数,前两个不用说,最后一个参数是要存放我们遍历的结果的,这种方式在以前的博客中也有介绍过。

大体看一下这个方法,虽然很长,但是不难,switch case中的语句只需要了解一个剩下的就都了解了。

首先定义了4个变量,先有点印象。接着一个for循环,for循环也是做android的很熟悉的,就是去遍历游标,如果你还不清楚这里,建议去看看android原生api。

18行,我们获取Class的实例,实例化主要是为将表中的数据查询保存到我们的java bean中。

接下来19行,又是一个for循环,这个循环是循环的什么呢? 恩,这里是循环的该行的所有字段。

20行,获取当前index的字段名。

接下来一个switch case,这里我们只去观察第一个case语句中的代码。

25行是去拼凑一个setter,不过这个有点特殊,有一个二目运算符,我先解释一下这是为什么,还记得我曾经提到过id是opendroid默认添加的,而不需要我们去定义bean字段吗? 而且我们在分析创建数据库的源码中也看到了自动创建_id字段的代码,但是现在就有一个问题了。 我们没有id字段,id怎么放bean中呢? 所以在OpenDroid类中我强制定义了一个id字段,而且在使用的过程中,你也应该发现你的bean实例会有一个getId和setId的方法。看源码来证明一下吧:

private int id;
public int getId() {
    return id;
}  

public void setId(int id) {
    this.id = id;
}

知道了这一点,我们再来理解这句话,如果查询的是_id字段,那么我们就去调用setId这个方法,否则就去调用getMethodName()根据当前数据库中的字段名拼凑出一个setter方法。

好吧,接着分析代码,27~28行,反射出我们拼凑的这个方法,并通过invoke去执行该方法,到这里,比如我们要获取stuName字段,那么Student.stuName就有值了。

在获取完当前行数据后,46行将生成的这个对象添加到ArrayList中保存起来。

总之,该方法的作用就是通过反射去实例化对应的类,并通过类中的setter方法设置值。

这里面还用到了一个getMethodName()方法,这个方法很简单,就是根据数据库字段名拼凑一个setXXX方法。

/**
 * 根据字段名构建方法名
 * @param columnName
 * @return
 */
public static String getMethodName(String columnName) {
    String methodName = columnName;
    methodName = Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1);
    methodName = "set" + methodName;  

    return methodName;
} 

很简单,不多说了,唯一需要注意的是第8行,意思是将字段名的首字母大写。

至此,一个find方法的流程就分析完了,其实剩下的find方法都是调用了同一个CRUD.query方法去查询的。

/**
 * 根据id查询数据
 * @param klass klass 映射的bean
 * @param id 要查询数据的id
 * @return  查询结果
 */
public <T extends OpenDroid> T find(Class<T> klass, int id) {
    List<T> result = CRUD.query(klass, mCocumns, "_id=?",
            new String[] { String.valueOf(id) }, null, "1",
            sSqliteDatabase);
    return result.size() > 0 ? result.get(0) : null;
} 

这个方法调用了CRUD.query方法去查询的,只是我们确定了条件。

public <T extends OpenDroid> List<T> find(Class<T> klass, int... ids) {
    StringBuilder builder = new StringBuilder("_id in (");
    String[] whereArgs = new String[ids.length];
    buildIn(builder, whereArgs, ids);  

    return CRUD.query(klass, mCocumns, builder.toString(), whereArgs,
            mOrder, mLimit, sSqliteDatabase);
} 

这个方法同样的原理,只需要是多个id,只要是多个id的,我们都是通过in语句去实现的,所以调用了buildIn方法去拼凑了一个in语句,剩下的和其他的find方法一样。

好啦,至此为止,opendroid的查询操作我们也介绍完了,也就是opendroid的所有CRUD操作有顺了一遍,那是不是opendroid所有功能都完了呢? 当然不是,还有我们的数据库升级方案呢! 所以下篇博客将会介绍opendroid的数据库升级方案!

最后是opendroid的开源地址:http://git.oschina.net/qibin/OpenDroid

时间: 2024-10-23 11:46:07

打造android ORM框架opendroid(六)——级联查询的相关文章

打造android ORM框架opendroid(七)——数据库升级方案

在上一篇博客<打造android ORM框架opendroid(六)--级联查询>我们讲了OpenDroid最后一块功能查询的实现原理.今天我们将进行OpenDroid一个重头戏,也是本系列博客的最后一篇--数据库升级方案. 说道数据库升级,我可是很头疼的, 为什么呢? 因为以前的项目中,根本没有考虑数据库升级方案的问题,就直接drop table了,这样导致的结果就是"以前的数据都消失了".额... 凭空消失确实不是很少的一件事,如果数据不重要还行,重要数据呢? 说消失就

打造android ORM框架opendroid(四)——优雅的删除数据

在上一篇博客<打造android ORM框架opendroid(三)--持久化数据>中,我们感受到了opendroid保存数据的流程,今天的博客我们来顺一下opendroid是如何删除数据的. 还记得我们在第一篇博客<打造android ORM框架opendroid(一)--ORM框架的使用>中介绍过opendroid的使用,先来回顾一下怎么利用opendroid来删除数据吧. int length = OpenDroid.delete(Student.class, 1, 2, 3

打造android ORM框架opendroid(五)——数据更新的实现

在上一篇博客<打造android ORM框架opendroid(四)--优雅的删除数据>中,我们介绍了opendroid是如何优雅的从数据库中删除数据的,也可以看到opendroid的设计是如此的简单,其实opendroid只是我作为兴趣或者说是抱着试试的态度写的,当然它肯定存在诸多不足,但是这并不影响我们去了解一个orm框架的流程. 废话不说了,下面进入主题,今天我选择去了解的是opendroid的update流程,其实,对于已经了解了delete操作的朋友们来说,今天的update流程肯定

打造android ORM框架opendroid(二)——自动创建数据库

在上一篇博客<打造android ORM框架opendroid(一)--ORM框架的使用>中相信你已经了解了opendroid的使用,那么从这篇博客开始,我们正式进入opendroid的源码分析,打造一款自己的ORM框架! 在正式开始之前,你需要保证手里有一份opendroid的源码,如果还没下载opendroid,请到http://git.oschina.net/qibin/OpenDroid 下载opendroid的源码. 任何数据库操作都是从创建数据库开始的,今天我们就来看看opendr

打造android ORM框架opendroid(三)——持久化数据

在上一篇博客<打造android ORM框架opendroid(二)--自动创建数据库>中,我们介绍了opendroid是怎么做到自动帮我们创建好数据库并通过反射拼凑出创建数据库的SQL语句,接着上面的博客,今天要来介绍一下opendroid数据库持久化(也就是insert操作)是怎么一个流程. 废话不多少,我们马上进入主题. ... 还记得通过opendroid我们是如何将数据保存到数据库的吗? 当时是调用了从OpenDroid类继承过来的save方法,来回顾一下吧. Student stu

打造android ORM框架opendroid(一)——ORM框架的使用

一.我的看法 我记得曾经有一篇博客是介绍的litepal的使用,在这篇博客中我提到过:本来以为android本身提供的API已经封装的够好了,根本不需要什么ORM框架了,但是在使用了litepal后,我感觉使用ORM框架还是很有必要的,下面是我对ORM和android API的几点看法: 1.做为API级别, android只能广义封装,而不能特定去封装一个API,所以android 对sqlite的封装已经很强大了. 2.作为开发者,我们需要为项目提供足够适配的解决方案,可能ORM框架比API

Android Orm框架(GreenDao)

Android Orm框架(GreenDao) 分类: android2014-04-10 14:29 723人阅读 评论(0) 收藏 举报 GreenDao与Ormlite对比 Ormlite:简单好用,比较符合JavaEE开发者使用习惯,注解很方便: GreenDao:为Android大大优化 ,最小的内存使用 ,非常高的性能优势. 官网地址:http://greendao-orm.com/features/ 项目地址:https://github.com/greenrobot/greenD

Android ORM 框架之 ActiveAndroid应用基础

ActiveAndroid作为轻量级的ORM框架,在快速开发中,使用很简单,满足大部分对数据库操作不复杂的应用. 一,配置 添加依赖 build.gradle中添加: repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'

Android ORM 框架之 greenDAO 使用心得

原文:http://itangqi.me/android/using-greendao-experience/ 前言 我相信,在平时的开发过程中,大家一定会或多或少地接触到 SQLite.然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等.所以,适用于 Android 的ORM 框架也就孕育而生了,现在市面上主流的框架有 OrmLite.SugarORM.Active Android.Realm 与 GreenDAO.而今天的主角便是 greenDAO,下面,我将