(七)——数据库升级方案
最后更新于:2022-04-01 06:34:06
在上一篇博客[《打造android ORM框架opendroid(六)——级联查询》](http://blog.csdn.net/qibin0506/article/details/43370135)我们讲了OpenDroid最后一块功能查询的实现原理。今天我们将进行OpenDroid一个重头戏,也是本系列博客的最后一篇——数据库升级方案。
说道数据库升级,我可是很头疼的, 为什么呢? 因为以前的项目中,根本没有考虑数据库升级方案的问题,就直接drop table了,这样导致的结果就是“以前的数据都消失了”。额。。。 凭空消失确实不是很少的一件事,如果数据不重要还行,重要数据呢? 说消失就消失了? 用户升级了一下软件,结果数据全没了。。。 那是多吊丝的一件事。
OpenDroid则提供了一种数据库升级的方案,当然这种方案肯定不是完美的。 肯定还有更好的方案,如果你发现你有好的解决方案,请不吝赐教。
好,下面开始进入正题。首先说说我的方案的原理吧:其实很简单,就是在drop table之前将数据查询出来,并保存到集合中,在创建新表后,尝试去insert数据。原理的思路很简单,以至于我一直认为这种方案太烂了, 可我没有想到更好的结果方案,也就只能先这样了。
大家都知道,android的SQLiteOpenHelper类中提供了一个抽象方法onUpgrade()来让用户灵活的定制数据库升级方案, 最简单的方法就是我之前提到直接drop table。既然upgrade的权利掌握在我们手中,那我们何不借onUpgrade()大干一番呢?
先来看看代码:
~~~
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
System.out.println("upgrade database");
upgrade(db);
}
~~~
在onUpgrade里除了一句打印,其实真正有用了就一句代码,当然也是调用了我们自定义的一个方法,那么我们就从upgrade()这个方法开始说起:
~~~
/**
* 升级数据库
* @param db 数据库链接
*/
private extends OpenDroid> void upgrade(SQLiteDatabase db) {
try {
XmlPullParser pullParser = Xml.newPullParser();
InputStream inputStream = DroidApplication.sContext.getAssets().open("open_droid.xml");
pullParser.setInput(inputStream, "utf-8");
int type = pullParser.getEventType();
while(type != XmlPullParser.END_DOCUMENT) {
if(type == XmlPullParser.START_TAG) {
// 获取mapping
if(pullParser.getName().equals(OpenDroidHelper.TAG_MAPPING)) {
dumpData(db, pullParser);
}
}
type = pullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
// 执行创建数据库
onCreate(db);
}
~~~
7~9行可以看出我们准备去解析open_droid.xml文件了,和我们平时解析一样,使用一个while循环,观察while循环,我们在15~17行获取到了有用的信息,如果当前的tag是mapping的或,我们又去调用了dumpData,这里面XmlPullParser会作为第二个参数传递过去。
方法的最后,我们直接调用了重载的onCreate方法去创建新表,当然还有数据的恢复。这个我们稍后分析,接下来我们来看看dumpData方法。
~~~
/**
* 将数据库中的数据转储到程序中
* @param db 数据库连接
* @param pullParser
* @throws Exception
*/
private extends OpenDroid> void dumpData(SQLiteDatabase db, XmlPullParser pullParser)
throws Exception {
Class klass = (Class) Class.forName(pullParser.getAttributeValue(null, "class"));
String tableName = klass.getSimpleName(); // 表名
Cursor cursor = db.rawQuery("select * from " + tableName, null);
T t;
Method m;
String methodName;
String columnName;
// 遍历游标
for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
t = (T) klass.newInstance(); // 通过反射进行实例化
final int columnCount = cursor.getColumnCount();
for(int i=0;i
columnName = cursor.getColumnName(i); // 获取字段名
// try一下,如果没有该字段对应的方法,则消化异常,并继续
try {
switch (cursor.getType(i)) {
case Cursor.FIELD_TYPE_INTEGER:
methodName = columnName.equals("_id") ? "setId" :
CRUD.getMethodName(cursor.getColumnName(i));
m = klass.getMethod(methodName, int.class); // 反射出方法
m.invoke(t, cursor.getInt(i)); // 执行方法
break;
case Cursor.FIELD_TYPE_FLOAT:
methodName = CRUD.getMethodName(cursor.getColumnName(i));
m = klass.getMethod(methodName, float.class);
m.invoke(t, cursor.getFloat(i));
break;
default:
methodName = CRUD.getMethodName(cursor.getColumnName(i));
m = klass.getMethod(methodName, String.class);
m.invoke(t, cursor.getString(i));
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
mOldData.add(t);
}
cursor.close();
db.execSQL("drop table if exists " + tableName); // 删除旧的数据库
}
~~~
这个方法很长,而且也很关键,我们的数据库升级方案将在这里终结。
第9行,我们通过Class.forName获取了一个Class, 是根据什么映射呢?来看一下我们open_droid.xml文件就一目了然。
~~~
open-droid>
version value="6" />
name value="school" />
mapping class="org.loader.opendroid.Student" />
mapping class="org.loader.opendroid.Grade" />
open-droid>
~~~
这这个xml中,我们就是要通过org.loader.opendroid.Student来映射出一个类。
第10行,我们获取了该类的类名,当然也就是我们要操作的表名了,唉? 为什么就一个表呢?
仔细看看这个方法是在哪调用的,我们是在一个循环中调用了,也就是在循环中去遍历xml节点,每次获取到mapping节点,都来调用一下这个方法。
11行,我们执行一段select语句,将现在表中所有的数据查询出来,那查询出来的数据我们怎么处理呢?
要回答这个问题,我们就得去下面的for循环中找答案。
在for循环中,20行,通过反射实例化了上面那个类,为什么要在循环中实例化呢?因为每行数据我们都需要用一个对象来保存。
21行,获取了当前行所有列的个数。
接下来有一个for循环,这个循环我们是循环的每一行的列,在循环中去取每一列的数据。
26行,进入一个switch语句,依照惯例,我们只去分析一个case语句。
在第一个case中,如果改列的字段是一个integer类型,28行,我们和之前讲过的一样去拼装一个setter,当然如果是_id的话,我们就直接定义为setId了。
30行,反射出这个方法,等待下面去执行。
当然31行我们就要去执行这个方法了,我们都知道setter方法是需要参数的,参数当然就是我们查询出来的当前列的数据了。
48行,我们将这个对象的实力放入一个集合中。
当查询完当前表,这个表就没用了,因为我们已经把数据都保存起来了,所以在51行,将该表删除。
至此,我们就把数据从旧版本的数据库中全部查询出来了。接下来我们回到onCreate方法中来看看新表是如果创建的,并且数据是如何恢复的。
~~~
@Override
public void onCreate(SQLiteDatabase db) {
for(String sql : OpenDroidHelper.getDBInfoBean().getSqls()) {
db.execSQL(sql);
}
// 还原数据
if(!mOldData.isEmpty()) {
for(OpenDroid item : mOldData) {
item.save(db);
}
}
}
~~~
前面几行代码,我们在[《打造android ORM框架opendroid(二)——自动创建数据库》](http://blog.csdn.net/qibin0506/article/details/42773281) 已经讲解过,这里就不重复了,我们重点来看看在那篇博客中省略的几行代码,也正是这几行代码,实现了旧数据向新表中的转移。
8行,先去判断mOldData是否为空的集合,因为onCreate方法并不是只有在数据库升级的时候才去执行。
接下来遍历整个集合,并且调用每个item的save方法将数据保存到新表中,当然这里我们重用了OpenDroid类中的save方法,因为都是insert嘛。从这里我们也可以看出这个mOldData集合的泛型肯定是OpenDroid。
`private ArrayList mOldData = new ArrayList(); `
好了,至此,我们opendroid提供的一个简单的数据库升级方案就执行完了,而且我们的opendroid也介绍的差不多了,剩下的一点东西都是辅助性的东西。哦,对了,这里还要提一点:细心的朋友可能已经发现了,opendroid在操作完数据库并没有默认的关闭掉数据库,而是蛋疼的提供了open和release两个方法,不信可以看代码:
~~~
/**
* 打开数据库
*/
public static void open() {
if(sSqliteDatabase == null) {
sSqliteDatabase = sOpenHelper.getWritableDatabase();
}
}
/**
* 释放数据库
*/
public static void release() {
if(sSqliteDatabase != null && sSqliteDatabase.isOpen()) {
sSqliteDatabase.close();
}
sSqliteDatabase = null;
}
~~~
这是为什么呢? 其实刚开始我是做了默认关闭了,可就是在写到数据库升级恢复数据的时候,因为save是在一个循环中执行了,因此,可能在很短的时间内多次开启/关闭数据库,这样做会消耗很大的性能,所以android会抛出一个异常,在一气之下,我就将代码改造成了这种方式,如果有大神有更好的解决方案,请赐教哈。
好了,至此我们opendroid系列博客也就尾声了,当然,做出一个orm框架本身并不重要,重要的是学会如何去做一个orm框架,别人能做的事,我们为什么就不能呢?对吧,作为一个程序员,我们要努力去做一个“创造者”,而不是简单停留在一个“使用者”上。
最后是opendroid的开源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)
(六)——级联查询
最后更新于: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)
(五)——数据更新的实现
最后更新于:2022-04-01 06:34:02
在上一篇博客[《打造android ORM框架opendroid(四)——优雅的删除数据》](http://blog.csdn.net/qibin0506/article/details/43083057)中,我们介绍了opendroid是如何优雅的从数据库中删除数据的,也可以看到opendroid的设计是如此的简单,其实[opendroid](http://git.oschina.net/qibin/OpenDroid)只是我作为兴趣或者说是抱着试试的态度写的,当然它肯定存在诸多不足,但是这并不影响我们去了解一个orm框架的流程。
废话不说了,下面进入主题,今天我选择去了解的是opendroid的update流程,其实,对于已经了解了delete操作的朋友们来说,今天的update流程肯定是如此的熟悉,因为它的大体流程和delete操作基本一致, 只是多了一步数据的对应。
按照惯例,我们先来熟悉一下opendroid是如何更新一条数据的。
~~~
Student stu = new Student();
stu.setStuName("loader");
stu.update(4);
~~~
~~~
Student stu = new Student();
stu.setStuName("loader");
stu.update("_id>?", "4");
~~~
~~~
ContentValues cv = new ContentValues();
cv.put("stuName", "loader");
OpenDroid.update(Student.class, cv, "_id>? or name like ?", "1", "%q%");
~~~
好了,opendroid就提供了这三种方式的更新操作。其中第三种是使用ContentValues的形式更新,相信大家肯定很熟悉了。
哦对了,提前透漏一下,其实和delete一样,这三种方式最后还是会归位到一个方法上。
首先我们来定位到第一个update的源码上。
~~~
/**
* 更新数据
* @param ids 要更新数据的id
* @return 影响行数
*/
public int update(int... ids) {
try {
Class klass = (Class) getClass();
ContentValues cv = new ContentValues();
generateData(klass, cv);
if(ids.length == 0) {
return update(klass, cv, null, null);
}
StringBuilder builder = new StringBuilder("_id in (");
String[] whereArgs = new String[ids.length];
buildIn(builder, whereArgs, ids);
return update(klass, cv, builder.toString(), whereArgs);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
~~~
第8行,我们获取了当前对象的Class,目的就是要通过反射来获取它里面的字段和名字。
接下来new了一个ContentValues对象,还记得上面我说过“这三种方式最后还是会归位到一个方法上“, 从这里我们大体可以猜到,最后都归位到
` OpenDroid.update(Student.class, cv, "_id>? or name like ?", "1", "%q%"); `
这个方法上了。保留着这个猜测,继续分析代码。
紧接着一个generateData方法,这个方法我们曾经在[《打造android ORM框架opendroid(三)——持久化数据》](http://blog.csdn.net/qibin0506/article/details/42872361)中详细的去分析过,如果你好不清楚这个方法的作用,可以去参考前面的博客。
12~14行,通过判断如果没有传任何id,则可能是更新全部数据,这里调用了静态的update方法,最后两个参数都传的null。
16~18这三行代码,大家应该还熟悉了吧,我们在讲解delete的时候也有这么三句话,这三句话就是构建一个in的sql语句,并设置它的参数,如果你不太熟悉它,可以参考[《打造android ORM框架opendroid(四)——优雅的删除数据》](http://blog.csdn.net/qibin0506/article/details/43083057),这篇博客中对buildIn方法也做了说明,这里就不重复去说了。
该方法的最后,同样调用了一个静态的update方法,并返回了影响行数。
接下来,我们来定位到第二个update方法中一探究竟!
~~~
/**
* 更新数据
* @param where 条件
* @param whereArgs 条件参数
* @return 影响行数
*/
public int update(String where, String... whereArgs) {
try {
Class klass = (Class) getClass();
ContentValues cv = new ContentValues();
generateData(klass, cv);
return update(klass, cv, where, whereArgs);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
~~~
其实这个方法和上面的很想,只是上面的需要在update方法里去组合条件,而这里的条件由用户传入,所以这个方法我们也不多讲了,接下来我们来看看三兄弟会合的地方,也就是第三方方式调用的那个静态方法!(哦,对了,这里验证上面我们那个猜测是正确的!)
~~~
/**
* 更新数据
* @param klass 要更新的表对应的class
* @param cv 要更新的数据
* @param where where条件
* @param whereArgs 条件的参数
* @return 影响行数
*/
public static extends OpenDroid> int update(Class klass, ContentValues cv,
String where, String... whereArgs) {
String tableName = klass.getSimpleName();
return CRUD.update(tableName, cv, where, whereArgs, sSqliteDatabase);
}
~~~
对于代码控来说,确实够令人失望了,代码只有两行!还没有注释多! 尼玛!(心里,千万只草泥马飞奔而过)
1/2行代码获取了klass的类名,也就是我们要操作的表名。
2/2行,直接调用了CRUD.update方法去更新数据库,又是CRUD...我们去看看吧。
~~~
/**
* 更新数据
* @param tableName 表名
* @param cv 更新的数据
* @param where 更新的条件
* @param whereArgs 更新的条件参数
* @param db 数据库
* @return 影响行数
*/
protected static extends OpenDroid> int update(String tableName, ContentValues cv,
String where, String[] whereArgs, SQLiteDatabase db) {
int count = db.update(tableName, cv, where, whereArgs);
return count;
}
~~~
好吧,又是两行代码, 而且直接调用了SQLiteDatabase的update方法去更新数据,走到这一步,要使用的数据肯定已经都准备好了。tableName我们在前面已经获取了,ContentValues是用过generateData来填充的或者用户自己new的,where和whereArgs不管是根据id更新还是通过条件更新的,我们都已经确定了。这里只需要调用android原生的update方法去更新数据库就ok了,最后返回了影响行数。
今天的update操作确实够简单,原因是,我们的很多代码都是在前面几篇博客中详细说明了,如果你还不清楚里面的一些细节,可以再花几分钟去看看前面的几篇博客。
来总结一下opendroid的update流程吧。
1、在业务逻辑中可以通过三种update方法去更新数据,除了一种静态的,另外两种都需要在bean对象上操作。
2、不管哪种操作,最后都会到OpenDroid.update(Class klass, ContentValues cv,String where, String... whereArgs)这个方法上。
3、接着通过CRUD.update方法来调用android原生的upate方法来更新数据库,并依次返回影响行数。
在完成了update操作的分析后,opendroid的基本操作已经完成了一大部分,现在只剩下select操作没去分析,相信大家现在完全可以照这opendroid的思路去做一个自己的ORM框架了。当然,下面的博客还会继续完成select操作的分析,当然还会有opendroid的数据库升级机制。
opendroid的开源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)
(四)——优雅的删除数据
最后更新于:2022-04-01 06:34:00
在上一篇博客[《打造android ORM框架opendroid(三)——持久化数据》](http://blog.csdn.net/qibin0506/article/details/42872361)中,我们感受到了opendroid保存数据的流程,今天的博客我们来顺一下opendroid是如何删除数据的。
还记得我们在第一篇博客[《打造android ORM框架opendroid(一)——ORM框架的使用》](http://blog.csdn.net/qibin0506/article/details/42736807)中介绍过opendroid的使用,先来回顾一下怎么利用opendroid来删除数据吧。
~~~
int length = OpenDroid.delete(Student.class, 1, 2, 3);
System.out.println(length);
~~~
还有一种删除数据的方式是使用where条。
~~~
int length = OpenDroid.delete(Student.class, "_id>?", "5");
System.out.println(length);
~~~
opendroid就这么两种删除数据的方式,但这两种方式已经可以适合大部分的需求了。
上面是回顾了一下opendroid的使用,但是我们今天的主题是了解opendroid是如何从数据库中把数据删除的。
所以...老规矩,首先定位到
` OpenDroid.delete(Student.class, 1, 2, 3); `
从最简单开始入手吧!
~~~
/**
* 删除数据
* @param klass 要删除的表对应的class
* @param ids 数据的ids
* @return 影响行数
*/
public static extends OpenDroid> int delete(Class klass, int... ids) {
if(ids.length == 0) {
return delete(klass, null, null);
}
StringBuilder builder = new StringBuilder("_id in (");
String[] whereArgs = new String[ids.length];
buildIn(builder, whereArgs, ids);
return delete(klass, builder.toString(), whereArgs);
}
~~~
根据id删除的这个方法并不长,我们一句一句来分析一下。
首先8~10行,是一个判断如果没有设置id,则调用一个静态的delete方法,这个delete方法我们稍候去看,现在我们接着往下走代码。
12行,用了一个StringBuilder来初始化一个in语句,从这里我们可能已经清楚,使用这个delete方法删除数据其实就是使用了sql语句的in操作。
13行,new了一个String类型的数组,数组的长度正好是ids的长度,为下面组件in语句做准备。
14行,我们调用了buildIn方法来构建一个in语句。
15行,直接调用了一个重载的delete的方法去删除数据,这里提一下,这个delete方法和我们上面if语句中调用的delete方法是同一个。
下面我们再来看看buildIn这个方法吧。
~~~
/**
* 组装IN语句
* @param builder in语句
* @param whereArgs in的内容
* @param ids 要拼装的ids
*/
private static void buildIn(StringBuilder builder, String[] whereArgs,
int... ids) {
if(ids.length > 1) {
for(int i=0;i1;i++) {
whereArgs[i] = String.valueOf(ids[i]);
builder.append("?,");
}
}
whereArgs[ids.length - 1] = String.valueOf(ids[ids.length - 1]);
builder.append("?)");
}
~~~
纵观整个buildIn方法也没什么难点,这里的作用就是拼装一个 _id in(?,?,?)这样形式的语句,并将ids放到上面我们初始化的那个String数据中,这样我们是不是就已经准备好了sql预处理中where的条件和条件的参数了呢?
当然这里还有两点可能感到迷惑的:
1、该方法为什么没有返回值? 这种写法可能不是很常见,就是将返回值以参数的形式传递进来,大家都知道java的参数传递是按引用传递,所以只要不改变该参数指向地址,在该方法中对参数内容的修改是会影响调用者的。
2、for循环中为什么是ids.length-1?因为我们需要的是(?,?,?)这样的形式,最后一个?后是没有“,”的,所以我们需要先拼装前length-1个,然后在循环外面进行了最后的收尾工作。
这里可能又会有朋友注意到,最后的收尾是在if外面进行的,这里是不是写错了? 这里是没有错的! 请看if的条件是什么!
分析完buildIn后,我们再返回那个delete方法中,最后一句是调用了另一个delete方法,至于参数,大家肯定已经清楚参数的值了。
下面再来看看这个重载的delete方法吧。
~~~
/**
* 删除数据
* @param klass 要删除的表对应的class
* @param where where条件
* @param whereArgs where条件的参数
* @return 影响行数
*/
public static extends OpenDroid> int delete(Class klass, String where,
String... whereArgs) {
String tableName = klass.getSimpleName();
return CRUD.delete(tableName, where, whereArgs, sSqliteDatabase);
}
~~~
哈哈,only两行代码,而且我们也惊奇的发现,这个方法也是我们上面第二种删除的方式,太好了,两种删除方式最后还是会在同一个方法中碰面!
如此简单,怎么下手呢? 第1行代码获取了klass的类名,这里代表的当然是我们删除操作的表明了。
下一行中我们又去调用了CRUD.delete方法,那么我们跟进代码,去看看CRUD.delete吧。
~~~
/**
* 删除数据
* @param tableName 表名
* @param where where条件
* @param whereArgs where条件的参数
* @param db 数据库
* @return 影响行数
*/
protected static extends OpenDroid> int delete(String tableName, String where,
String[] whereArgs, SQLiteDatabase db) {
int count = db.delete(tableName, where, whereArgs);
return count;
}
~~~
又是两行代码!
首先来看看参数吧,第一个参数是要操作的表名,第二个参数是where条件,第三个参数是where条件的参数,第四个参数当然是我们操作数据库的句柄了。
在代码体中,第一句就去调用了android原生的delete操作去根据条件删除数据,返回值是删除的条目个数,紧接着返回该个数,CRUD.delete方法执行完毕!
这个删除的条目个数会再次最多经过两次返回,最终返回到我们的业务逻辑中。
最后,我们来回顾一下这个流程吧。
1、我们的代码调用OpenDroid.delete方法
2、不管调用哪个重载的OpenDroid.delete方法,最后都会来到使用条件删除的那个delete方法
3、使用id删除,无非就是根据传入的id和id个数来组件一个SQL的in语句
4、最后,在CRUD的delete方法中完成了数据的删除,并返回影响行数。
opendroid的删除操作我们已经顺了一遍,看起来挺高端的,一看源码是不是感觉如此简单?
到现在为止,我们已经顺完了opendroid的插入数据和删除数据,在接下来的博客中,还会继续完成opendroid更新数据和查询数据的代码。
(三)——持久化数据
最后更新于:2022-04-01 06:33:57
在上一篇博客[《打造android ORM框架opendroid(二)——自动创建数据库》](http://blog.csdn.net/qibin0506/article/details/42773281)中,我们介绍了opendroid是怎么做到自动帮我们创建好数据库并通过反射拼凑出创建数据库的SQL语句,接着上面的博客,今天要来介绍一下opendroid数据库持久化(也就是insert操作)是怎么一个流程。
废话不多少,我们马上进入主题。
...
还记得通过opendroid我们是如何将数据保存到数据库的吗? 当时是调用了从OpenDroid类继承过来的save方法,来回顾一下吧。
~~~
Student stu = new Student();
stu.setStuName("亓斌");
stu.setStuAge(18);
stu.save();
~~~
这样我们就把student的信息保存到了Student表中了,关于Student表是怎么创建的,前面一篇博客中已经介绍过了,今天的重点就是这个save()方法。
按照惯例,我们定位到save()的源码。
~~~
/**
* 插入数据
* @return 最后插入的id
*/
public long save() {
try {
Class klass = (Class) getClass();
ContentValues cv = new ContentValues();
generateData(klass, cv);
return CRUD.insert(klass.getSimpleName(), cv, sSqliteDatabase);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
~~~
这个方法还是比较简单的,只有11行代码,我们来一句句的分析一下。
首先第7行,通过getClass方法获取了当前代表当前对象的Class。
接着第8行new了一个ContentValues,相信大家对它肯定再熟悉不过了。
9行,调用了generateData方法,这个方法有两个参数,第一个是一个Class对象,第7行的时候我们已经获取了,第二个参数是一个ContentValues,用来保存存放将要插入数据库的数据。
接着在11行,调用了CRUD类中的insert静态方法来保存数据,insert方法的第一个参数是要插入的表名,因为我们的表和bean是一一对应的,所以当前类名就是我们要操作的表名,第二个参数是一个SQLiteDatabase对象。
CRUD.insert方法会返回一个long类型的返回值,相信大家已经猜到了,返回值就是我们新插入数据的id。
分析完这个方法,接下来我们就来看看第9行中调用的generateData方法.
~~~
/**
* 生成数据
* @param tableName 要获取的表名
* @param values 要获取的数据
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private void generateData(Class klass, ContentValues values)
10. throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
Field[] fields = klass.getDeclaredFields(); // 获取类中的所有字段
Method m;
String fieldName;
String methodName;
for (Field field : fields) {
// 如果是public,则忽略
if (field.isAccessible()) {
continue;
}
// 获取字段的类型
Class fieldType = field.getType();
fieldName = field.getName(); // 获取字段名称
// 将字段名称的首字母大写,准备拼装getter(getName)
methodName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
// 这里还要判断一下类型是不是boolean,
// 如果是boolean, 就不是getXXX了,而是isXXX
if (fieldType == Boolean.class || fieldType == boolean.class) {
m = klass.getDeclaredMethod("is" + methodName);
} else {
m = klass.getDeclaredMethod("get" + methodName); // 获取方法
}
// 获取方法的返回值
Object value = m.invoke(this);
// 对于一些类型不支持。。
// 未找到解决方案
if (value == null) {
// 如果是null,则在contentValues里添加一个null
// values.putNull(fieldName);
continue;
}
// 通过判断field的类型,向contentValues插入对应的数据
if (fieldType == Integer.class || fieldType == int.class) {
values.put(fieldName, Integer.parseInt(m.invoke(this).toString()));
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
values.put(fieldName, Boolean.parseBoolean(m.invoke(this).toString()));
} else {
values.put(fieldName, m.invoke(this).toString());
}
}
}
~~~
在今天要讲解的方法中,这个方法算是最长的了,没关系,我们一行行的来看。
方法的参数我们在上面已经说明了,这里就不重复了,在方法刚开始,接连不断的定义了4个变量,第一个变量是我们通过klass获取到的该类中所有的字段,接下来我们要通过这些字段来获取要保存的值和字段名。剩下的几个变量我们在用到的时候再来说。
17行,进入了一个for循环,遍历的所有的字段,循环中,19~21行,我们依然去判断该字段是不是public,如果是public,则证明该字段并没有映射到数据库中。
24行,获取了字段的类型,因为在下面我们要通过类型来设置值。
26~28行,我们准备去拼凑getter方法。
接着32~36行,又是一个判断,主要是为了我们java bean定义的规范,如果是boolean类型的,我们在定义方法的时候难道不是isXXX吗?
ok,我们通过反射获取了将要调用的的方法,接下来39行就要通过getter方法来获取具体的值了。
42~46行是一个败笔,还未能结果对于没考虑到的类型的怎么去支持,不过也不影响,常用的类型已经支持了。
49~55行,通过字段的类型,来向ContentValues中保存不同类型的值,可以看到我们使用调用m.invoke方法来执行反射出来的方法。
至此,我们已经将数据都保存进ContentValues中了,接下来就是调用android原生的api代码,将数据库保存进数据库就ok了。
而保存进数据库肯定就是调用了CRUD.insert方法,赶紧来看看这个方法吧。
~~~
/**
* 插入数据
* @param t 对应的bean
* @param db 数据库操作
* @return 最新插入的id
*/
protected static long insert(String tableName, ContentValues cv, SQLiteDatabase db) {
long id = db.insert(tableName, null, cv);
return id;
}
~~~
太幸福了! 只有两行代码! 看看吧,我们直接调用了传过来的SQLiteDatabase的insert方法将数据保存进了数据库。相信看到这里,大家对save的整个流程应该有了大概的认知。
好,opendroid的数据持久化流程就说到这里,在接下来的博客中我们还会去了解opendroid的其他操作。
opendroid的开源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)
(二)——自动创建数据库
最后更新于:2022-04-01 06:33:55
在上一篇博客[《打造android ORM框架opendroid(一)——ORM框架的使用》](http://blog.csdn.net/qibin0506/article/details/42736807)中相信你已经了解了opendroid的使用,那么从这篇博客开始,我们正式进入opendroid的源码分析,打造一款自己的ORM框架!
在正式开始之前,你需要保证手里有一份opendroid的源码,如果还没下载opendroid,请到[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid) 下载opendroid的源码。
任何数据库操作都是从创建数据库开始的,今天我们就来看看opendroid是怎么帮我们自动创建数据库的。 还记得关系映射怎么配置吗? 在open-droid.xml中,通过配置mapping节点来告诉opendroid我们需要映射的java bean。那么数据库操作是从何时开始的呢, 拿insert来说,就是调用了从OpenDroid继承而来的save()方法!在这之前,我们没有任何数据库方面的操作,那么我们就从save()方法开始,看看opendroid是怎么创建数据库的。
~~~
/**
* 插入数据
* @return 最后插入的id
*/
public long save() {
try {
Class klass = (Class) getClass();
ContentValues cv = new ContentValues();
generateData(klass, cv);
return CRUD.insert(klass.getSimpleName(), cv, sSqliteDatabase);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
~~~
第11行,通过调用了CRUD的一个静态方法insert将数据保存到数据库中,insert的最后一个参数sSqliteDatabas是我们关心的,来看看它的定义:
~~~
private static SQLiteDatabase sSqliteDatabase = sOpenHelper.getWritableDatabase();
~~~
sSqliteDatabase是通过sOpenHelper调用getWriteableDatabase()返回的,相信这里大家应该非常熟悉了,再来看看sOpenHelper的定义:
` private static CreateDB sOpenHelper = new CreateDB(); `
在这里直接new了一个CreateDB,通过类名我们完全可以知道CreateDB就是创建数据库的关键。来看看CreateDB吧:
~~~
public class CreateDB extends SQLiteOpenHelper {
public CreateDB() {
super(DroidApplication.sContext, OpenDroidHelper.getDBInfoBean().getName(),
null, OpenDroidHelper.getDBInfoBean().getVersion(), new DefaultDatabaseErrorHandler());
}
@Override
public void onCreate(SQLiteDatabase db) {
for(String sql : OpenDroidHelper.getDBInfoBean().getSqls()) {
db.execSQL(sql);
}
}
}
~~~
这里我只截取了和创建数据库有关的代码, 可以看到CreateDB继承自SQLiteOpenHelper,这里大家肯定也很熟悉,先从构造方法开始看, 在构造方法中直接调用了父类的构造方法,第一个参数是一个context对象,这个对象是在DroidApplication中,其实很简单,就是在onCreate中调用getApplicationContext()为DroidApplication中的sContext静态变量赋值,这里就不贴代码了,可以在源码中找到,在看看接下来几个参数,都是通过OpenDroidHelper.getDBInfoBean获取的。再往后看看发现onCreate中也是通过遍历OpenDroidHelper.getDBInfoBean().getSqls()来获取创建表的sql语句,那么现在我们就去OpenDroidHelper看看吧。
~~~
public class OpenDroidHelper {
public static final String TAG_DROID = "open-droid";
public static final String TAG_VERSION = "version";
public static final String TAG_NAME = "name";
public static final String TAG_MAPPING = "mapping";
private static DBBean sDBBean;
public static DBBean getDBInfoBean() {
if(sDBBean == null) {
generateDBInfoBean();
}
return sDBBean;
}
/**
* 解析Asserts目录下的open_droid.xml文件,生成DBInfoBean
*/
private static void generateDBInfoBean() {
try {
XmlPullParser pullParser = Xml.newPullParser();
InputStream inputStream = DroidApplication.sContext.getAssets().open("open_droid.xml");
pullParser.setInput(inputStream, "utf-8");
int type = pullParser.getEventType();
String tagName = null;
while(type != XmlPullParser.END_DOCUMENT) {
if(type == XmlPullParser.START_TAG) {
tagName = pullParser.getName();
if(tagName.equals(TAG_DROID)) {
sDBBean = new DBBean();
}else if(tagName.equals(TAG_VERSION)) {
// 获取版本号
sDBBean.setVersion(Integer.parseInt(pullParser.getAttributeValue(null, "value")));
}else if(tagName.equals(TAG_NAME)) {
// 获取数据库名
sDBBean.setName(pullParser.getAttributeValue(null, "value"));
}else if(tagName.equals(TAG_MAPPING)) {
// 获取所有建表语句
sDBBean.addSql(generateSql(pullParser));
}
}
type = pullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成建表sql语句
* @param pullParser
* @return
* @throws ClassNotFoundException
* @throws XmlPullParserException
* @throws IOException
*/
private static String generateSql(XmlPullParser pullParser)
throws ClassNotFoundException, XmlPullParserException, IOException {
// 反射获取class
Class klass = (Class) Class.forName(pullParser.getAttributeValue(null, "class"));
StringBuilder sql = new StringBuilder("create table ");
// 获取类名, getSimpleName获取类名, getName()获取包名+类名
sql.append(klass.getSimpleName()).append("(");
// 自动创建一个_id
sql.append("_id integer primary key autoincrement,");
// 获取所有的字段
Field[] fields = klass.getDeclaredFields();
for(Field field : fields) {
// 如果是public的, 则表示不是一个表的字段
if(field.isAccessible()) {
continue;
}
// 获取字段名
String name = field.getName();
sql.append(name).append(" ");
// 获取字段类型
Class fieldType = field.getType();
if(fieldType == String.class) { // 如果是String
sql.append("text,");
}else if(fieldType == Integer.class || fieldType == int.class) {
sql.append("integer,");
}else if(fieldType == Long.class || fieldType == long.class){
sql.append("integer,");
}else if(fieldType == Boolean.class || fieldType == boolean.class) {
sql.append("boolean,");
}else if(fieldType == Float.class || fieldType == float.class) {
sql.append("float,");
}
}
sql.replace(sql.length() - 1, sql.length(), "");
sql.append(");");
return sql.toString();
}
}
~~~
额,代码有点小长, 我们慢慢来看。首先来看看我们之前调用的getDBInfoBean(),这个方法很简单,就是返回了一个DBBean对象,不过,它还调用了generateDBInfoBean()方法,通过方法名可以看出,它的作用是生成DBBean的。
~~~
/**
* 解析Asserts目录下的open_droid.xml文件,生成DBInfoBean
*/
private static void generateDBInfoBean() {
try {
XmlPullParser pullParser = Xml.newPullParser();
InputStream inputStream = DroidApplication.sContext.getAssets().open("open_droid.xml");
pullParser.setInput(inputStream, "utf-8");
int type = pullParser.getEventType();
String tagName = null;
while(type != XmlPullParser.END_DOCUMENT) {
if(type == XmlPullParser.START_TAG) {
tagName = pullParser.getName();
if(tagName.equals(TAG_DROID)) {
sDBBean = new DBBean();
}else if(tagName.equals(TAG_VERSION)) {
// 获取版本号
sDBBean.setVersion(Integer.parseInt(pullParser.getAttributeValue(null, "value")));
}else if(tagName.equals(TAG_NAME)) {
// 获取数据库名
sDBBean.setName(pullParser.getAttributeValue(null, "value"));
}else if(tagName.equals(TAG_MAPPING)) {
// 获取所有建表语句
sDBBean.addSql(generateSql(pullParser));
}
}
type = pullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
~~~
恩,在generateDBInfoBean这个方法中,都是我们熟悉的XMLPullParser的代码,作用就是去解析open-droid.xml文件,获取数据库名称、数据库版本和数据表的信息。虽然很长,但是都很简单,20行,我们获取了数据库的版本号,并保存到了DBBean中,同样的23行获取了数据库的名称,注意第26行,我们想DBBean中添加的sql语句,那添加的什么sql语句呢? 肯定是建表的sql语句了。来看看generateSql()方法。
~~~
/**
* 生成建表sql语句
* @param pullParser
* @return
* @throws ClassNotFoundException
* @throws XmlPullParserException
* @throws IOException
*/
private static String generateSql(XmlPullParser pullParser)
throws ClassNotFoundException, XmlPullParserException, IOException {
// 反射获取class
Class klass = (Class) Class.forName(pullParser.getAttributeValue(null, "class"));
StringBuilder sql = new StringBuilder("create table ");
// 获取类名, getSimpleName获取类名, getName()获取包名+类名
sql.append(klass.getSimpleName()).append("(");
// 自动创建一个_id
sql.append("_id integer primary key autoincrement,");
// 获取所有的字段
Field[] fields = klass.getDeclaredFields();
for(Field field : fields) {
// 如果是public的, 则表示不是一个表的字段
if(field.isAccessible()) {
continue;
}
// 获取字段名
String name = field.getName();
sql.append(name).append(" ");
// 获取字段类型
Class fieldType = field.getType();
if(fieldType == String.class) { // 如果是String
sql.append("text,");
}else if(fieldType == Integer.class || fieldType == int.class) {
sql.append("integer,");
}else if(fieldType == Long.class || fieldType == long.class){
sql.append("integer,");
}else if(fieldType == Boolean.class || fieldType == boolean.class) {
sql.append("boolean,");
}else if(fieldType == Float.class || fieldType == float.class) {
sql.append("float,");
}
}
sql.replace(sql.length() - 1, sql.length(), "");
sql.append(");");
return sql.toString();
}
~~~
generateSql()里面全是反射的代码,如果你对反射还不熟悉,建议你先去看看java反射,因为opendroid中大量使用了反射机制,
12行,通过反射获取我们要映射的class,然后14~18行,是初始化创建表的sql语句,并且可以看到opendroid会自动为我们添加一个_id字段,所以在定义bean的时候,我们不需要再次定义了。
21行,获取了这个类中定义的所有字段,并在22行循环遍历这些字段。
14~26行,可以看到,如果字段是public的,那就忽略它,所以如果你不想把某个字段映射到数据库中,就定义成public的。
29~30行,是向创建表的sql语句中追加表名。
33~44行,通过判断这个字段类型,来向创建表的sql语句中追加该字段的类型。
46行的作用是删除最后一个的“,”
47行,就完成了该表的创建sql语句。
至此,opendroid的自动创建数据库的流程我们就分析完了,现在来总结一下:
1、数据库的创建我们借助了android API的SQLiteOpenHelper。
2、在SQLiteOpenHelper的onCreate中遍历我们自动生成的建表语句并执行。
3、如何生成建表语句? 在OpenDroidHelper中通过反射获取类的类名和字段名,并通过StringBuilder来拼凑建表语句。
4、为了方便,我们在OpenDroidHelper中将解析出来的数据名、数据库版本和建表语句都放到了DBBean中,那么我们在CreateDB类中就可以通过DBBean方便的获取数据库的信息了。
ok,数据库的自动创建大体流程就是这样,在接下来的博客中,我们还会去一一介绍opendroid的CRUD操作和它的数据库升级机制。
马上继续[《打造android ORM框架opendroid(三)——持久化数据》](http://blog.csdn.net/qibin0506/article/details/42872361)
opendroid的开源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)
(一)——ORM框架的使用
最后更新于:2022-04-01 06:33:53
一、我的看法
我记得曾经有一篇博客是介绍的litepal的使用,在这篇博客中我提到过:本来以为android本身提供的API已经封装的够好了,根本不需要什么ORM框架了,但是在使用了litepal后,我感觉使用ORM框架还是很有必要的,下面是我对ORM和android API的几点看法:
1、做为API级别, android只能广义封装,而不能特定去封装一个API,所以android 对sqlite的封装已经很强大了。
2、作为开发者,我们需要为项目提供足够适配的解决方案,可能ORM框架比API更加适合项目。
所以说,ORM框架和原生API并不冲突,并不是使用了ORM就得抛弃原生API,甚至,我们可以利用原生API进行二次封装,封装出一个ORM框架,这也是这小系列博客的主题——利用原生API打造我们自己的ORM框架。
二、进入主题
开篇,要先介绍一个opendroid这个自己的ORM框架,然后使用几分钟时间去学会使用这个框架,这里学会使用并不是目的,而是要在知道怎么用的前提下,去实现一个ORM。当然opendroid(我的ORM框架名称叫opendroid,是不是很骚的一个名字?)的开发仅用了零零散散的4天的时间,所以目前仅支持最基本的CRUD和数据库升级方案,而且很有可能存在大量BUG,不过目的已经达到了,就是要做一个自己的ORM。
三、使用opendroid
假如你之前用过ORM框架(不管是什么平台上的),那么你很快就能上手opendroid,如果你没有使用过ORM,也没关系,相信只需要一遍就可以搞定!
1、首先需要下载opendroid,现在这个项目我开源到了git@osc上,可以到下面地址下载jar包和源代码
[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)
2、下载后,将library中的opendroid.jar复制到项目的libs目录下。
3、修改AndroidMenifest.xml文件的Application,添加anrdroid:name="android:name="org.loader.opendroid.app.DroidApplication"
4、新建将要与数据库映射的java bean。
5、在asserts目录下新建open_droid.xml文件,并将一下代码复制到open_droid.xml文件中
~~~
xml version="1.0" encoding="utf-8"?>
open-droid>
name value="school" />
version value="1" />
mapping class="org.loader.opendroid.Student" />
open-droid>
~~~
这个xml中,name节点指定了数据库的名称;version节点指定了数据库的版本,在以后的开发中只需要修改value的值,就可以实现数据库的升级;mapping可能会用多个,指定了与数据表映射的java bean,这里的Student对应的数据库中肯定会有一个Student表。
6、创建与数据库映射的java bean文件
~~~
package org.loader.opendroid;
import org.loader.opendroid.db.OpenDroid;
public class Student extends OpenDroid {
private String stuName;
private int stuAge;
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public int getStuAge() {
return stuAge;
}
public void setStuAge(int stuAge) {
this.stuAge = stuAge;
}
}
~~~
很普通的一个java bean,定义了两个字段,并提供getter和setter方法,需要注意到是Student继承了OpenDroid类,这个类是我们opendroid库中的一个类。
7、开始使用opendroid的CRUD功能
在上面配置好了以后,我们就可以在业务中很容易的实现CRUD功能了,
7.1、插入数据:
~~~
Student stu = new Student();
stu.setStuName("亓斌");
stu.setStuAge(18);
stu.save();
~~~
在一系列set以后,只需要调用Student中的save方法就可以将数据保存到数据库中,这里你可能会有两个疑问:1)、数据库在哪创建的?2)、save方法哪里来的?在Student里并没有看见这个方法。
回答上面的问题:
1)、数据库在哪创建的?—— 在有数据库操作的时候,opendroid会自动帮我们创建好数据库和mapping中指定实体的表。
2)、save方法哪里来的?—— 当然是从OpenDroid中继承过来的,以后的博客在实现opendroid的过程中,会说到这个方法。
7.2、更新数据
opendroid提供了一系列的方法来简化update操作。
~~~
Student stu = new Student();
stu.setStuName("loader");
stu.update("_id>?", "4");
~~~
上面的代码是将id大于4的stuName更新为loader,这里你可能又有疑问了:Student中我们并没有定义id啊,这里怎么可以使用id呢? 是这样的, 在定义实体类的时候并需要创建id字段,opendroid会自动帮我们创建一个_id字段。
还有什么更新操作呢?
~~~
Student stu = new Student();
stu.setStuName("loader");
stu.update(4);
~~~
上面的代码是将id为4的stuName更新为loader
当然,你也可以使用ContentValues进行更新:
~~~
ContentValues cv = new ContentValues();
cv.put("stuName", "opendroid");
OpenDroid.update(Student.class, cv, "_id>?", "8");
~~~
很容易就理解了,这是将id大于8的stuName更新为opendroid.
好了,更新操作就说这么多,相信,已经够应付日常的开发工作了。
7.3、删除数据
opendroid的删除功能也很简单,并且也像上面代码这么直观。
~~~
int length = OpenDroid.delete(Student.class, 1, 2, 3);
System.out.println(length);
~~~
一行代码就可以搞定删除,上面代码是删除id为1或2或3的数据。
使用条件删除
~~~
int length = OpenDroid.delete(Student.class, "_id>?", "5");
System.out.println(length);
~~~
上面的代码的作用是删除id大于5的数据
在CRUD中相对麻烦点的就是查询操作了,当然opendroid对查询操作也进行了封装。
~~~
Student result = OpenDroid.query.find(Student.class, 1);
System.out.println(result.getStuName());
~~~
上面的代码是查询id为1的数据,并将查询结果映射到Student类中。
~~~
List result = OpenDroid.query.find(Student.class);
for(Student res : result) {
System.out.println(res.getStuName());
}
~~~
哈哈,把条件省略就是查询所有数据了,当然这里返回的List集合。
~~~
List result = OpenDroid.query.find(Student.class, 1, 5, 10);
for(Student res : result) {
System.out.println(res.getId() + " : " + res.getStuName());
}
~~~
查询id为1或5或10的数据,注意是调用的OpenDroid中一个静态变量query的方法。
` List result = OpenDroid.query.columns("stuName", "stuAge").where("_id>?", "5").order("_id DESC").limit(3) `
~~~
.find(Student.class);
for(Student res : result) {
System.out.println(res.getStuName() + " : " + res.getStuAge());
}
~~~
当然query中还提供了一系列方法来设置查询语句中的where、order、limit等,
columns()方法是指定要查询的字段
where()方法是指定查询的条件
order()方法是指定查询数据的排列方式
limit()方法是指定select语句的limit
最后调用find方法将数据查询出来。
不知不觉,我们已经将opendroid中最主要的crud操作基本全部掌握了,相信很容易就可以理解这里代码,下面来看看如何使用opendroid升级数据库。
1、修改open-droid.xml文件中version字段的value值。
2、添加或删除mapping,或者根据业务升级需求调整某个实例类的字段
只需要上面两步,在下次使用数据库的时候,opendroid就会自动帮我们升级数据库,并且会将现在的数据更新到新的表中,完全不用担心数据库升级过程中数据的丢失,当然我们抛弃的字段opendroid会自动忽略。
一篇博客的时间,我们已经掌握可opendroid的使用,绝大多数的ORM框架的用法也大差不差,所以你也一并掌握了ORM的基本语法。
在接下来的几篇博客中,我会一步步的去讲解opendroid是如何实现的,最终打造出一个属于你自己的ORM框架!
马上继续[《打造android ORM框架opendroid(二)——自动创建数据库》](http://blog.csdn.net/qibin0506/article/details/42773281)
前言
最后更新于:2022-04-01 06:33:50
> 原文出处:[打造android ORM框架](http://blog.csdn.net/column/details/opendroid.html)
> 作者:[亓斌](http://blog.csdn.net/qibin0506)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 打造android ORM框架
> 实现一个简单的android ORM框架,从此告别android api烦人的参数。