SQliteDataBase相信大家都不陌生,目前有很多第三方框架,比如:GreenDao、DbFlow和Room等等,相信也都了解或者用过,但是我们今天不讲这些框架,目前我们项目中也用到数据库,但是根据目前框架有些需求并不能很好的符合我们的项目,所以就有了这系列文章,可能会有2篇关于数据库的文章,分别是:数据库基本操作与分库和数据库的升级,这些文章不会讲数据库的基本使用,我们将的是封装。
数据库设计三大范式:
- 1、原子性:表示我们在设计数据库时字段不能在拆分;
- 2、唯一性:该数据在我们的数据库中不存在重复性;
- 3、避免冗余性:一般是主键和外键的操作,就是两个表中不能存在重复的数据;
数据库作用
- 1、在Android开发中,数据库在中小型APP使用并不广泛,但其对于数据管理的方便性是其他数据存储工具无法取代的;
- 2、特点:数据集中管理,控制冗余,提高数据的利用率和一致性,有利于程序的开发和维护;
如何设计数据库框架?
- 1、如何自动创建数据库?
- 2、如何自动创建数据表?
- 3、如何让用户使用方便?
- 4、如何确保性能最优?
- 5、 将类名和属性名转换为创建数据表的sql语句?
类图


开始设计我们的数据库框架
public class BaseDao<T> implements IBaseDao<T> {
//持有数据库操作的引用
private SQLiteDatabase mSqLiteDatabase;
//数据表名
private String mTableName;
//操作数据所对应的类型,
private Class<T> mEntityClass;
//标识是否已经初始化
private boolean mIsInit = false;
//定义一个缓存Map key是数据库字段名 value是Class成员变量
//为什么需要做这个缓存,第一是为了性能优化,
// 第二是后面再查询,通过反射把Cursor设置给entity比较方便,
// 第三是数据库字段和entity进行映射
private HashMap<String, Field> mCacheMap_DbRecord_EntityField;
//数据库初始阿虎,需自动创建表
public boolean init(SQLiteDatabase sqLiteDatabase, Class<T> entityClass) {
this.mSqLiteDatabase = sqLiteDatabase;
this.mEntityClass = entityClass;
if (!mIsInit) {
//根据传入的class 进行数据表的创建
DbTable dbTable = entityClass.getAnnotation(DbTable.class);
if (dbTable != null && !TextUtils.isEmpty(dbTable.value())) {
mTableName = dbTable.value().toLowerCase();
} else {
mTableName = entityClass.getSimpleName().toLowerCase();
}
//处理关键字 order
if (mTableName.equalsIgnoreCase("order")) {
mTableName = mTableName + "_info";
}
//数据库没有打开
if (!sqLiteDatabase.isOpen()) {
return mIsInit = false;
}
//得到创建数据表的sql 串
String createTableSql = getCreateTableSql();
Log.e("tag", createTableSql);
sqLiteDatabase.execSQL(createTableSql);
mCacheMap_DbRecord_EntityField = new HashMap<>();
initCacheMap();
return mIsInit = true;
}
return mIsInit = false;
}
}
BaseDao是我们把数据库的基本操作全部抽取出来,而一般Dao是对应数据库中的的一张表,首先我们先看看init方法,init方法是根据传入的EntityClass 进行数据表的创建,这个比较简单,在看看initCacheMap方法:
private void initCacheMap() {
//取得所有的列名
String sql = "select * from " + mTableName + " limit 1,0";
Cursor cursor = mSqLiteDatabase.rawQuery(sql, null);
cursor.close();
String[] columnNames = cursor.getColumnNames();
//取得所有的成员变量
Field[] fields = mEntityClass.getDeclaredFields();
for (Field field : fields) field.setAccessible(true);
for (String columnName : columnNames) {
Field fieldColumn = null;
for (Field field : fields) {
String fieldName;
DbFiled dbFiled = field.getAnnotation(DbFiled.class);
if (dbFiled != null && !TextUtils.isEmpty(dbFiled.value())) {
fieldName = dbFiled.value();
} else {
fieldName = field.getName();
}
if (columnName.equals(fieldName)) {
fieldColumn = field;
break;
}
}
if (fieldColumn != null) {
mCacheMap_DbRecord_EntityField.put(columnName, fieldColumn);
}
}
}
定义一个缓存Map key是数据库字段名 value是EntiytClass成员变量为什么需要做这个缓存,第一是为了性能优化,第二是后面操作时通过反射把Cursor设置给entityClass比较方便,第三是数据库字段和entity进行映射。
上面BaseDao定义好之后,我们需要在定义一个管理数据库的单例类来管理数据库的所有表的Dao:
/**
* @Describe: 管理者该数据库的所有表, 而且公共数据只有一个
*/
public class BaseDaoFactory {
//防止同一个数据库同一张表存在多个BaseDao,key是entityClass,value是dao
//因为公共数据库只存在一个,那么我们之缓存dao,即:dao的缓存池
private WeakHashMap<Class<?>, BaseDao> mDaoWeakHashMap;
private SQLiteDatabase mSqLiteDatabase;
private static String sDbName;
protected static String sDbRootPath;
//你可以认为一个设备上只能存在一个数据库
protected BaseDaoFactory() {
if (TextUtils.isEmpty(sDbName)) throw new RuntimeException("must call init method");
this.mSqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sDbName, null);
this.mDaoWeakHashMap = new WeakHashMap<>();
}
private static class BaseDaoFactoryHolder {
private static final BaseDaoFactory INSTANCE = new BaseDaoFactory();
}
public static BaseDaoFactory getInstance() {
return BaseDaoFactoryHolder.INSTANCE;
}
/**
* 使用前必须初始化
*
* @param dbName data/data/xx.xx/my.db
*/
public static void init(Context context, String dbName) {
File file = new File("data/data/" + context.getPackageName());
if (!file.exists()) {
file.mkdirs();
}
BaseDaoFactory.sDbRootPath = file.getAbsolutePath();
BaseDaoFactory.sDbName = sDbRootPath + "/" + dbName;
}
/**
* @param daoClass 获取到指定Dao
* @param entityClass 对应数据表的实体类,也就是表
* @param <DAO> 获取到指定Dao
* @param <ENTITY> 对应数据表的实体类
* @return 具体的实体类
* <p>
* create table if not exists order(desc TEXT,id INTEGER)
* create table if not exists tb_user(id INTEGER,name TEXT,password TEXT)
*/
@Nullable
public <DAO extends BaseDao<ENTITY>, ENTITY> DAO getBaseDao(@NonNull Class<DAO> daoClass, Class<ENTITY> entityClass) {
// 每一张表对应一个dao
BaseDao baseDao = mDaoWeakHashMap.get(entityClass);
try {
if (baseDao == null) {
synchronized (BaseDaoFactory.class) {
baseDao = mDaoWeakHashMap.get(entityClass);
if (baseDao == null) {
baseDao = daoClass.newInstance();
// 每一张表对应一个dao,为了解决自动创建表,必须一个表对应一个dao
boolean isSuccess = baseDao.init(mSqLiteDatabase, entityClass);
if (isSuccess) {
mDaoWeakHashMap.put(entityClass, baseDao);
}
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return (DAO) baseDao;
}
}
在BaseDaoFactory 中管理者该数据库的所有表, 并且对每一个Dao做了缓存,避免每次操作还要重新创建Dao造成不必要的性能开销,BaseDaoFactory 管理者该数据库的所有表, 而且公共数据只有一个,为什么这么说呢?因为目前我们的项目用到数据库分库,我们需要把公共数据库和用户的私有数据库分开来管理,而BaseDaoFactory就是管理者我们的公共数据库,那接下来看看如何分库?
public class BaseDaoSubFactory extends BaseDaoFactory {
private static final BaseDaoSubFactory instance = new BaseDaoSubFactory();
//数据库连接池,key:dbpth value : WeakHashMap<Class<?>,BaseDao>
// WeakHashMap<Class<?>,BaseDao> :防止同一个数据库同一张表存在多个BaseDao,key是entityClass,value是dao
private WeakHashMap<String, WeakHashMap<Class<?>, BaseDao>> mDbWeakHashMap;
private BaseDaoSubFactory() {
super();
mDbWeakHashMap = new WeakHashMap<>();
}
public static BaseDaoSubFactory getInstance() {
return instance;
}
/**
* 定义一个用于分库的数据库对象
*/
private SQLiteDatabase subSqLiteDatabase;
@Override
@Nullable
public <DAO extends BaseDao<ENTITY>, ENTITY> DAO getBaseDao(@NonNull Class<DAO> daoClass, Class<ENTITY> entityClass) {
String databaseValue = PrivateDbPathHelper.getValue(sDbRootPath);
BaseDao baseDao = getDao(entityClass, databaseValue);
try {
//baseDao为null,有两种情况,1、首次创建数据库;2、首次使用dao
if (baseDao == null) {
synchronized (BaseDaoSubFactory.class) {
baseDao = getDao(entityClass, databaseValue);
if (baseDao == null) {
subSqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(databaseValue, null);
// 每一张表对应一个dao,为了解决自动创建表,必须一个表对应一个dao
baseDao = daoClass.newInstance();
boolean isSuccess = baseDao.init(subSqLiteDatabase, entityClass);
if (isSuccess) {
//从数据库缓存池中获取对应的dao
WeakHashMap<Class<?>, BaseDao> daoWeakHashMap = mDbWeakHashMap.get(databaseValue);
if (daoWeakHashMap != null) {
daoWeakHashMap.put(entityClass, baseDao);
}
}
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return (DAO) baseDao;
}
/**
* 私有数据库可能存在多个,所以我们需要缓存起来,并且要把统一数据中dao也缓存起来
*
* @param entityClass
* @param databaseValue
* @param <ENTITY>
* @return
*/
@Nullable
private <ENTITY> BaseDao getDao(Class<ENTITY> entityClass, String databaseValue) {
//从数据库databaseValue中拿到对应的dao的缓存池
WeakHashMap<Class<?>, BaseDao> daoWeakHashMap = mDbWeakHashMap.get(databaseValue);
if (daoWeakHashMap == null) {
//管理以前的数据库,注意关闭的是私有数据库
if (subSqLiteDatabase != null && subSqLiteDatabase.isOpen()) {
subSqLiteDatabase.close();
}
//清空以前的dao缓存池
mDbWeakHashMap.clear();
// 如果dao的缓存池不存在,那么创建一个dao的缓存池,并加入该数据库缓存池中
daoWeakHashMap = new WeakHashMap<>();
mDbWeakHashMap.put(databaseValue, daoWeakHashMap);
}
//从获取dao缓存池中获取到指定的dao
return daoWeakHashMap.get(entityClass);
}
}
可以看到BaseDaoSubFactory 继承BaseDaoFactory ,并且在BaseDaoSubFactory 中因为我们切换数据,我们需要把每一个数据库dao缓存,并且是根据数据库进行区分的。
多表查询:select * from person,dept where person.did = dept.did;
网友评论