一直以来都觉得创建数据库和插入查询等代码都是一个重复性的工作,但是一直没有时间来整理,现在又要使用数据库了,但是又不想使用第三方那么麻烦的东西,所以自己写了一个个人觉得比较通用的东西,也可以根据自己的需求来进行修改。
如果对实现过程不感兴趣可以直接看后面总结的用法。
一.创建数据库和表
创建数据库的第一步不用说,继承SQLiteOpenHelper,下面是简单的创建数据库的实例:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "my.db";
public MyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//1.创建表
db.execSQL(DataBaseUtil.getCreateTable(UserBean.class));
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
大家可能发现了,这里面并没有一些表的名字和表的字段等等,而是使用了
DataBaseUtil.getCreateTable(UserBean.class),这样就省去了建表和字段的时间了。
那么来看下
DataBaseUtil.getCreateTable(UserBean.class)是怎么设计的:
/**
* 创建对应类的数据库表(目前只支持整型和字符串类型)
* @param clazz
* @return
*/
public static String getCreateTable(Class clazz) {
Map<String, String> map = ClassUtil.getDataBaseMap(clazz);
int size = map.size();
Iterator iterator = map.entrySet().iterator();
StringBuffer sb = new StringBuffer();
String per = "Create table if not exists " + clazz.getSimpleName() + " ( ";
sb.append(per);
sb.append("_id integer primay key,");
for(int i = 0; i < size; i ++){
Map.Entry<String, String> entry = (Map.Entry) iterator.next();
sb.append(entry.getKey() + " " + entry.getValue());
if(i == size - 1)
break;
sb.append(",");
}
sb.append(")");
return sb.toString();
}
以上的代码似乎看到头都大了,其实就是做了创建表的工作,是根据类的名称来作为表的名字,使用类的属性来做表的字段,最终返回创建表的字符串。
二.如何获取类对应的数据库字段和类型
细心的可能发现了
ClassUtil.getDataBaseMap(clazz)这个方法,我们来看下这个方法:
/**
* 获取:属性名称-属性对应的数据库类型
*
* @param clazz
* @return
*/
public static Map<String, String> getDataBaseMap(Class clazz) {
TreeMap<String, String> map = new TreeMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
int modifier = field.getModifiers();
if (Modifier.isFinal(modifier) || Modifier.isStatic(modifier))
continue;
String typeStr = field.getType().getSimpleName();
if (typeStr.equals("int")
|| typeStr.equals("long")) {
typeStr = "integer";
} else if (typeStr.equals("String")) {
typeStr = "text";
}else if(typeStr.equals("boolean")){
continue;
}
map.put(field.getName(), typeStr);
}
return map;
}
通过代码可以知道,暂时只做了对整型(int和long)以及String的属性支持,也就是说类里面的这些类型会被创建为数据库的字段,其实这里如果换成注解的方式,就是很多第三方数据库的一些用法。
二.插入数据到数据库
将数据插入到数据库的代码如下:
/**
* 将ContentBean加入到数据库
* @param bean
*/
public void insert(UserBean bean){
SQLiteDatabase db = mHelper.getWritableDatabase();
try {
ContentValues values = getFieldAndValueMap(bean);
db.insert(UserBean.class.getSimpleName(), null, values);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
其实如果理解了建表的过程,那么这个插入数据的过程也应该会猜到一些,获取ContentValues也是根据对应类的字段来设置其对应的值,那么来看看
getFieldAndValueMap这个方法:
/**
* 将对应到属性和值存储到数据库
* @param obj
* @return
*/
public ContentValues getFieldAndValueMap(Object obj) throws IllegalAccessException {
ContentValues values = new ContentValues();
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field: fields){
int modifier = field.getModifiers();
if(Modifier.isFinal(modifier) || Modifier.isStatic(modifier))
continue;
String typeStr = field.getType().getSimpleName();
field.setAccessible(true);
if(typeStr.equals("int")){
values.put(field.getName(), (int)field.getInt(obj));
}else if(typeStr.equals("long")){
values.put(field.getName(), (long)field.getLong(obj));
}else if(typeStr.equals("String")){
values.put(field.getName(), (String)field.get(obj));
}
}
return values;
}
以上就是利用反射来将类的属性对应的字段,设置对应的值,这个是通用的方法,但目前只支持int,long,String这3个类型(因为是本地存储和暂时的需求,有时间会完善下)。
调用的时候只需要
DataBaseUtil.getInstance(this).insert(userBean)即可完成数据的插入操作,这句代码包含如果没有创建数据库会创建数据库和需要创建的表,然后将userBean的数据插入到数据库中。
三.获取数据库数据
因为建表和插入都是用来反射,那么获取数据也必不可免,但是有个好处就是通用,怎么个通用法:获取任何类的存储数据都适用这个方法就够了。
看代码:
/**
* 获取给定类型(已经创建了该类对应到表)的数据库记录
* @param clazz
* @return
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchFieldException
*/
public ArrayList getList(Class clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
ArrayList list = new ArrayList();
SQLiteDatabase db = mHelper.getReadableDatabase();
ArrayList<String> fieldlist = ClassUtil.getDataBaseFieldStrings(clazz);
String[] projection = new String[fieldlist.size()];
for(int i = 0; i < fieldlist.size(); i ++){
projection[i] = fieldlist.get(i);
}
Cursor cursor = db.query(clazz.getSimpleName(), projection, null, null, null, null, null);
boolean hasCursor = cursor.moveToFirst();
while(hasCursor && cursor != null){
Object object = clazz.newInstance();
int index = 0;
String typeStr = "";
for(String columnName : projection){
index = cursor.getColumnIndex(columnName);
Field field = object.getClass().getDeclaredField(columnName);
field.setAccessible(true);
typeStr = field.getType().getSimpleName();
if(typeStr.equals("int")){
field.set(object, cursor.getInt(index));
}else if(typeStr.equals("long")){
field.set(object, cursor.getLong(index));
}else if(typeStr.equals("String")){
field.set(object, cursor.getString(index));
}
}
list.add(object);
if(!cursor.moveToNext()){
break;
}
}
return list;
}
好吧,写的时候没感觉什么,但是自己回头来看的时候也有点懵逼,其实流程很简单:获取所有字段-->查表获取Cursor-->创建实体类设置值-->加入到list中。
好了过程很复杂,其实都是为了使用的时候简单灵活,使用如下:
ArrayList list = DataBaseUtil.getInstance(this).getList(UserBean.class);
获取不同类的数据只需要将UserBean.class替换下就可以了。
四.总结下使用方法:
如果对实现过程不感兴趣,那么可以直接看这里了:
1.创建你需要存储类的表:
public void onCreate(SQLiteDatabase db) {
db.execSQL(getCreateTable(UserBean.class/*你想要存储的类*/));
}
2.如果想存储某个类的数据到数据库中使用(不用管数据库的创建和表的创建):
DataBaseUtil.getInstance(this).insert(userBean/*你的实体类*/);
3.获取数据:
DataBaseUtil.getInstance(this).getList(UserBean.class/*你想获取的类型的数据类(Class类型)*/)
注意:目前存储int, long,String这3个类型属性的数据。目前支持int,long,String,boolean类型属性的数据存储。
其实最大的好处就是将代码可以直接拿来使用,只需加入创建数据库表的类型(总结步骤1的db.execSQL)代码,插入和获取基本可以不动就能直接使用。
我在项目中已经测试通过,后面会提起出来放在github上面,如果有兴趣或有好的建议,欢迎大家来参与。