一、概述
Android数据库在存储数据方面很重要,我们项目当中一般用的SQLiteOpenHelper这个类进行数据库的创建,升级,然后在用数据库操作类(譬如UserDao)进行增删改查,整个设计都是面向过程的,可扩展性不高,现在我们以面向对象的角度来设计数据库。
整个设计所设计到的知识点:
泛型 注解 反射
数据库语句拼接
设计模式
1、单例模式 2、简单工厂模式 3、模板方法模式
对于简单工厂模式和模板方法模式不太懂的可以看我的这两篇博客
简单工厂模式
模板方法模式
二、整个的一个设计思路
我用一张图表示:
对于这张图,我解释几点
1、整张图就是调用端activity通过调用BaseDaoFactory实例化数据库操作类(userDao)插入一条数据。
2、调用层不关系数据库的创建在哪里创建,不关心表的创建,不关心sql语句的拼写。
3、BaseDaoFactory是创建BaseDao的工厂类,用到了工厂设计模式,其本身又是一个单例模式,BaseDao里面提供了创建表的语句,由具体操作数据库的子类去实现,这正体现了模板方法模式。
4、对象的成员变量和表的列名是一一对应关系,不是说表的字段名就一定是成员变量名称,用户可以通过注解设置在要操作的类的成员变量上,以此来产生映射关系。
三、具体代码细节
IBaseDao接口,提供数据库操作的方法(增删改查)
public interface IBaseDao {
//插入数据
Long insert(T t);
Long update(T entity,T where);
}
插入的数据不知道,用泛型表示
我们看他的实现类,BaseDao
public abstract class BaseDao implements IBaseDao {
private SQLiteDatabase mDatabase;
private boolean isInit = false; //保证实例化一次
//持有操作数据库表对应的Java类型
private Class entityClass;
//维护这表名与成员变量名的映射关系
private HashMap cacheMap;
//表名
private String tableName;
protected synchronized boolean init(Class entity, SQLiteDatabase sqLiteDatabase) {
if (!isInit) {
this.entityClass = entity; //给操作数据库表对应的类型赋值
mDatabase = sqLiteDatabase;
if (entityClass.getAnnotation(DbTable.class).value() == null) { 1
tableName = entityClass.getSimpleName();
} else {
tableName = entityClass.getAnnotation(DbTable.class).value();
}
if (!mDatabase.isOpen()) {
return false;
}
if (!TextUtils.isEmpty(createTable())) { 2 //创建表的语句
sqLiteDatabase.execSQL(createTable()); //创建表
}
//实体类的成员变量和表的字段名的映射关系的缓存集合
cacheMap = new HashMap<>();
initCacheMap(); 3
isInit = true;
}
return isInit;
}
//维护映射关系
private void initCacheMap() {
String sql = "select * from " + this.tableName + " limit 1 , 0";
Cursor cursor = null;
try {
cursor = mDatabase.rawQuery(sql, null);
String[] columns = cursor.getColumnNames(); //表的列名数组
//拿到fields数组
Field[] columsFileds = entityClass.getFields();
for (Field field : columsFileds) {
field.setAccessible(true); //暴力反射
}
//开始找对应关系
for (String columsName : columns) {
Field columFiled = null;
for (Field filed : columsFileds) {
String fieldName = null;
if (!TextUtils.isEmpty(filed.getAnnotation(DbFiled.class).value())) {
//如果字段上面有对应的数据库列名
fieldName = filed.getAnnotation(DbFiled.class).value();
} else {
fieldName = filed.getName();
}
//如果表的列名等于了成员变量的注解的名字或者成员变量的名字
if (fieldName.equals(columsName)) {
columFiled = filed; //赋值
break;
}
}
//找到了对应关系
if (columFiled != null) {
cacheMap.put(columsName, columFiled);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
}
//将T entity转换为map
public Map getValues(T entity) {
HashMap result = new HashMap<>();
Iterator fieldIterator = cacheMap.values().iterator();
// 循环遍历 映射map的 Filed
while (fieldIterator.hasNext()) {
Field colmunToFiled = fieldIterator.next();
String cacheKey = null;
String cacheValue = null;
if (colmunToFiled.getAnnotation(DbFiled.class).value() != null) {
cacheKey = colmunToFiled.getAnnotation(DbFiled.class).value();
} else {
cacheKey = colmunToFiled.getName();
}
try {
if (null == colmunToFiled.get(entity)) {
continue;
}
cacheValue = colmunToFiled.get(entity).toString();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
result.put(cacheKey, cacheValue);
}
return result;
}
/**
* 将map 转换为ContentValues
*
* @return
*/
public ContentValues getContentValues(Map map) {
ContentValues contentValues = new ContentValues();
Set keys = map.keySet();
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = map.get(key);
if (value != null) {
contentValues.put(key, value);
}
}
return contentValues;
}
//创建数据库表的语句
public abstract String createTable(); 6
@Override
public Long insert(T entity) {
5
Map map = getValues(entity);
ContentValues contentValues = getContentValues(map);
Long result = mDatabase.insert(tableName,null,contentValues);
return result;
}
@Override
public Long update(T entity, T where) {
return null;
}
}
分析下这个类,主要功能都集中在init方法里面。
在代码1处,通过if判断当期类有没有加上表名的注解,给表名tabName赋值。
代码2处通过判断创建表的语句是否为空,来创建表,createTable方法给具体子类实现,代码在6处。
在代码4处,初始化hashMap维护实体bean类的成员变量和表的字段名的映射关系,key为String类型,value为Filed类型。具体的逻辑请看代码。
在代码5处就是将map集合字段名和字段值转换为ContentValues,然后进行插入数据库操作。
这个类分析大概就这么多,涉及到的其他类的代码我这里贴一下
DbTable注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {
String value();
}
这个类就是给实体类指定一个表名没有指定默认为类名,譬如这里的User类
@DbTable("tb_user")
public class User {
public User(String name,String password){
this.password = password;
this.name = name;
}
public User(){
}
@DbFiled("password")
public String password;
@DbFiled("name")
public String name;
}
DbTable指定表名通过这个注解和数据库表名产生映射关系,这个User类的成员变量用到了DbFiled注解,用于指定字段名。贴代码:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbFiled {
String value();
}
这是数据库操作基类,我们接着看具体的操作类UserDao
public class UserDao extends BaseDao{
@Override
public String createTable() {
return "create table if not exists tb_user(name varchar(20),password varchar(10))";
}
}
这个类主要是就是返回了创建数据库表的语句,逻辑都是父类给处理了,指定了泛型。
好,我们最后看下数据库操作工厂类
/**
* Created by dell on 2017/7/16.
* 数据库操作工程类
*/
public class BaseDaoFactory {
private String mSqlitePath;
private SQLiteDatabase mSqliteDatabase;
private static BaseDaoFactory instance = new BaseDaoFactory(); 1
public BaseDaoFactory() {
mSqlitePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/student.db";2
openDataBase();3
}
public synchronized ,M> T getDataHelper(Class clazz,Class entityClass){
BaseDao baseDao = null;
try {
baseDao = clazz.newInstance();
baseDao.init(entityClass,mSqliteDatabase);
} catch (Exception e) {
e.printStackTrace();
}
return (T) baseDao;
}
private void openDataBase() {
this.mSqliteDatabase = SQLiteDatabase.openOrCreateDatabase(mSqlitePath, null);
}
public static BaseDaoFactory getInstance(){
return instance;
}
}
单例模式,路径放在sd卡的根目录,创建数据库,这是1,2,3处代码做的操作。
getDataHelper方法的泛型T表示BaseDao的子类,也就是具体的操作数据库类,继承自BaseDao由用户去实现,扩展性高,M表示插入的实体类型,通过反射实例化BaseDao然后调用init方法进行初始化。最后返回BaseDao的子类。也就是生产数据库操作类。
最后一步,调用端:
在MainActivity中我们这样操作:
public class MainActivity extends Activity {
UserDao userDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
}
public void save(View view) {
for (int i=0;i<4;i++){
User user = new User("lcty", "123456");
userDao.insert(user);
}
}
}
操作图和效果图搞起:
点击save保存四条数据
看插入的数据:
好,我们看到数据已经插入成功,分析完毕,有说的不对的地方望指正,多多交流。