(六)——级联查询

最后更新于:2022-04-01 06:34:04

在上一篇博客[《打造android ORM框架opendroid(五)——数据更新的实现》](http://blog.csdn.net/qibin0506/article/details/43148523)  我们介绍了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 extends OpenDroid> List find(Class klass) {             return CRUD.query(klass, mCocumns, mWhere, mWhereArgs,                      mOrder, mLimit, sSqliteDatabase);         }                      /**        * 根据id查询数据        * @param klass klass 映射的bean        * @param id 要查询数据的id        * @return  查询结果        */         public extends OpenDroid> T find(Class klass, int id) {             List result = CRUD.query(klass, mCocumns, "_id=?",                      new String[] { String.valueOf(id) }, null, "1",                     sSqliteDatabase);             return result.size() > 0 ? result.get(0) : null;         }                      public extends OpenDroid> List find(Class 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 extends OpenDroid> List find(Class 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 extends OpenDroid> List query(Class klass, String[] columns,              String where, String[] whereArgs, String order, String limit, SQLiteDatabase db) {         List resultList = new ArrayList(); // 存放结果         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 extends OpenDroid> void foreachCursor(Class klass,             Cursor cursor, List 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             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 extends OpenDroid> T find(Class klass, int id) {         List 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 extends OpenDroid> List find(Class 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](http://git.oschina.net/qibin/OpenDroid)
';