在Android中为开发者提供了SQLiteDatabase这个类且这个类提供了insert、delete、update、query。在我们平时开发中由于不像Web开发需要丰富的数据库功能,所以这四个已经足够满足我们了。所以开源世界里就冒出了很多功能强大的数据库开源框架,可以像我的应用中功能其实只是一点点不是很有必要把这么一个大框架都塞进去,说不定得不偿失。所以我们开始自己封装一个通用的数据库框架。
第一步我们都需要实现SQLiteOpenHelper这个数据库帮助类类创建数据、生成数据库表、数据库版本升级等功能,我们知道我们是通过SQL语句来创建的,类似:
public static final String CREATE_TABLE = "create table Person ("
+ "id integer primary key autoincrement, "
+ "name text, "
+ "age integer, "
+ "flag boolean)";
以前每次要是版本升级增加新的字段都得手动在这里做修改,所以版本上线后也总是害怕到底有没有改动到影响别的地方了呢,总之,这样处理有点麻烦和不爽。这里我们采用反射思想把一个Person类中的成员变量直接拿出来当做数据库字段然后直接将Person这个就作为数据库表名了。
final static class DatabaseSqliteHelper extends SQLiteOpenHelper {
private static final String TAG = "DatabaseSqliteHelper";
/**
* 数据库版本号
*/
public static final int DATABASE_VERSION = 1;
/**
* 数据库名字
*/
public static final String DATABASE_NAME = "neacy.db";
private Class clazz;
public DatabaseSqliteHelper(Context context, Class<?> _clazz) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
clazz = _clazz;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(createTableByDb());
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + DBUtils.getTableName(clazz));// 如果数据表存在就删除掉
db.execSQL(createTableByDb());
}
/**
* 创建一个数据库表语句
*/
private final String createTableByDb() {
StringBuilder sb = new StringBuilder();
String tableName = DBUtils.getTableName(clazz);
sb.append("create table ").append(tableName).append(" (id INTEGER PRIMARY KEY AUTOINCREMENT, ");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String fieldType = field.getType().getName();
String fieldName = field.getName();
if (fieldName.equals("_id") || fieldName.equals("id")) {
continue;
}
sb.append(fieldName).append(DBUtils.geteColumnType(fieldType)).append(", ");
}
int len = sb.length();
sb.replace(len - 2, len, ")");
Log.d(TAG, "SQL = " + sb.toString());
return sb.toString();
}
}
是不是以后看你要加多少个字段都不再犹豫和害怕我们只要修改下数据库版本号让系统重新去生成一个数据库表就好了。
数据库插入操作
在Android中系统为我们提供了insert方法来插入值。
public long insert(String table, String nullColumnHack, ContentValues values)
可是我们需要自己调用ContentValues中的put一遍又一遍来将数据存进去然后才保存到数据库。所以:
public long insert(Object object) {
Class<?> clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();
ContentValues values = new ContentValues();
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
if (fieldName.equals("id") || fieldName.equals("_id")) {
continue;
}
putContentValues(values, field, object);
}
long id = mHelper.getWritableDatabase().insert(DBUtils.getTableName(object.getClass()), null, values);
Log.w("Jayuchou", "insert_id == " + id);
return id;
}
/**
* 将数据写入到ContentValues
*/
private void putContentValues(ContentValues values, Field field, Object object) {
Class<?> clazz = values.getClass();
try {
Object[] parameters = new Object[] {field.getName(), field.get(object)};
Class<?>[] parameterTypes = getParameterTypes(field, field.get(object), parameters);
Method method = clazz.getDeclaredMethod("put", parameterTypes);
method.setAccessible(true);
method.invoke(values, parameters);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 得到反射方法中的参数类型
*/
private Class<?>[] getParameterTypes(Field field, Object fieldValue, Object[] parameters) {
Class<?>[] parameterTypes;
if (isCharType(field)) {
parameters[1] = String.valueOf(fieldValue);
parameterTypes = new Class[]{String.class, String.class};
} else {
if (field.getType().isPrimitive()) {
parameterTypes = new Class[]{String.class, getObjectType(field.getType())};
} else if ("java.util.Date".equals(field.getType().getName())) {
parameterTypes = new Class[]{String.class, Long.class};
} else {
parameterTypes = new Class[]{String.class, field.getType()};
}
}
return parameterTypes;
}
这样我们到时候在调用的地方只要简单的如下操作即可了:
Person person = new Person("Tom",18,false);
DBManager.insert(person);
省去了一大堆contentvalues的put方法,且可以适用于添加任何类到数据库中去。
数据库的修改、删除操作
对于删除功能,直接传入要删除的id就好了相对功能没有那么复杂
public void deleteById(Class<?> clazz, long id) {
long _id = mHelper.getWritableDatabase().delete(DBUtils.getTableName(clazz), "id=" + id, null);
Log.w("Jayuchou", "deleteById_id == " + _id);
}
调用的时候:
manager.deleteById(Person.class, 1);
对于修改也是差不多类似,但是需要传入一个修改的Contentvalues值
public void updateById(Class<?> clazz, ContentValues values, long id) {
long _id = mHelper.getWritableDatabase().update(DBUtils.getTableName(clazz), values, "id=" + id, null);
Log.w("Jayuchou", "updateById_id == " + _id);
}
调用的时候:
ContentValues values = new ContentValues();
values.put("age", 33);
manager.updateById(Person.class, values, 1);
数据库查找操作
这里提供了一个方法仅仅查找全部数据,因为无乱怎么查找最后都是返回一个Cursor对象然后解析出数据出来。所以我们把解析操作这个过程采用反射机制来处理,这样我们就通用于任何类的查找功能。
/**
* 查找全部数据
*/
public <T>List<T> findAll(Class<T> clazz) {
Cursor cursor = mHelper.getReadableDatabase().query(DBUtils.getTableName(clazz), null, null, null, null, null, null);
return getEntity(clazz, cursor);
}
/**
* 通过反射解析Cursor
*/
private <T>List<T> getEntity(Class<T> clazz, Cursor cursor) {
List<T> results = new ArrayList<T>();
if (cursor != null && cursor.moveToFirst()) {
try {
final int size = cursor.getCount();
Log.w("Jayuchou", "数据库count——size = " + size);
for (int i = 0; i < size; i++) {
Field[] fields = clazz.getDeclaredFields();
T modeClass = clazz.newInstance();
for (Field fd : fields) {
Class<?> cursorClass = cursor.getClass();
String colunmMethodName = getColumnMethodName(fd.getType());
Method cursorMethod = cursorClass.getMethod(colunmMethodName, int.class);
Object object = cursorMethod.invoke(cursor, cursor.getColumnIndex(fd.getName()));
// boolean值在数据的存储方式是0/1 所以需要转换
if (fd.getType() == boolean.class || fd.getType() == Boolean.class) {
if ("0".equals(String.valueOf(object))) {
object = false;
} else if ("1".equals(String.valueOf(object))) {
object = true;
}
}
String methodName = makeSetterMethodName(fd);
Method method = clazz.getDeclaredMethod(methodName, fd.getType());
method.invoke(modeClass, object);
}
Log.w("Jayuchou", "Result = " + ((Person) modeClass).getName());
results.add(modeClass);
}
} catch (Exception e) {
Log.w("Jayuchou", "出错了 === " + e.toString());
} finally {
cursor.close();
}
}
return results;
}
private String getColumnMethodName(Class<?> fieldType) {
String typeName;
if (fieldType.isPrimitive()) {
typeName = DBUtils.capitalize(fieldType.getName());
} else {
typeName = fieldType.getSimpleName();
}
String methodName = "get" + typeName;
if ("getBoolean".equals(methodName)) {
methodName = "getInt";
} else if ("getChar".equals(methodName) || "getCharacter".equals(methodName)) {
methodName = "getString";
} else if ("getDate".equals(methodName)) {
methodName = "getLong";
} else if ("getInteger".equals(methodName)) {
methodName = "getInt";
}
return methodName;
}
private String makeSetterMethodName(Field field) {
String setterMethodName;
String setterMethodPrefix = "set";
if (isPrimitiveBooleanType(field) && field.getName().matches("^is[A-Z]{1}.*$")) {
setterMethodName = setterMethodPrefix + field.getName().substring(2);
} else if (field.getName().matches("^[a-z]{1}[A-Z]{1}.*")) {
setterMethodName = setterMethodPrefix + field.getName();
} else {
setterMethodName = setterMethodPrefix + DBUtils.capitalize(field.getName());
}
return setterMethodName;
}
private boolean isPrimitiveBooleanType(Field field) {
Class<?> fieldType = field.getType();
if ("boolean".equals(fieldType.getName())) {
return true;
}
return false;
}
然后我们就可以简单的一行调用了:
List<Person> persons = manager.findAll(Person.class);
就这样一个采用反射机制类封装的数据库框架就完成了,能通用于很多种情况使用起来还是很简单的。像世面上那些优秀的数据库开源框架内部的原理基本上也是采用反射这种使其能够通用。
demo查看地址:https://github.com/Neacy/BlogCode/tree/master/app/src/main/java/com/database/demo
参考:http://www.codeceo.com/article/write-own-android-database.html