1.涉及知识
反射 工厂模式 单例
关于工厂模式:抽象工厂用于创建相对复杂的对象,适用于对象包含很多零件或生产大批对象的情景
关于泛型:
泛型方法
public class Test {
public static <T> T init(String s, Class<T> clazz){
s = clazz.getSimpleName();
System.out.println("s "+s);
return null;
}
class Person{
}
public static void main(String[] args) {
init(new String(), Person.class);
}
}
输出
s Person
public static <T> T init(String s, Class<T> clazz)中
<T>是声明泛型. 第二个T代表返回对象类型为T .Class<T> clazz代表传入参数是T的class
泛型类的使用
public interface IDaoSupport<T>{
}
一般泛型类/接口在类名之后就会加上 这里是泛型的声明 之后即可在类中是泛型T
2.缓存方案
我们知道访问数据库 访问网络都是比较耗时的,但是一般由于数据量较大,不可能全部缓存到本地,因此访问网络和数据库有些情况还是必不可少的,那么我们应该尽量减少从用户操作到用户看到返回结果之间的时间
为了提高搜索效率 ,有一些优化缓存的方案
当然现在一些http框架还会自带缓存,比如OKHttp 针对不同的网络框架,可能有不同的缓存方案。但是我们设计的缓存方案,应该是尽量通用的。
另外,缓存也可以设置定期清除的策略,避免空间被无用数据占用太多的空间
我们本节的代码主要是上面黑色箭头的第一步:查看本地是否有缓存的部分,说的简单一点,其实就是数据库的CURD
下面我们就进入数据库操作的学习
3.类结构
4.代码展示
Model类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
数据库支持类接口
public interface IDaoSupport<T> {
// 初始化数据库表(Class<T> clazz代表T所对应的类)
void init(SQLiteDatabase sqLiteDatabase, Class<T> clazz);
// 数据库插入数据
public long inert(T t);
public void deleteAll();
}
辅助类
class DaoUtil {
private static final String TAG = "DaoUtil";
// 获取数据库表名
public static String getTableName(Class<?> clazz) {
return clazz.getSimpleName();
}
// 将java中的基本数据类型翻译为SQL语句中的数据类型
// 此方法只适用于属性中的基本类型 如果存在复杂类型(如User Student)等 则不适用该方法
public static String getColumnType(String type) {
String value = null;
if (type.contains("String")) {
value = " text";
} else if (type.contains("int")) {
value = " integer";
} else if (type.contains("boolean")) {
value = " boolean";
} else if (type.contains("float")) {
value = " float";
} else if (type.contains("double")) {
value = " double";
} else if (type.contains("char")) {
value = " varchar";
} else if (type.contains("long")) {
value = " long";
} else {
Log.e(TAG, "getColumnType: invalid type!");
}
return value;
}
}
数据库支持类实现类
class DaoSupport<T> implements IDaoSupport<T> {
// 数据库对象
private SQLiteDatabase mSqLiteDatabase;
// 数据库需要操作的 表中存储的 对象类型
private Class<T> mClazz;
private String TAG = "DaoSupport";
public DaoSupport(SQLiteDatabase sqLiteDatabase, Class<T> clazz) {
init(sqLiteDatabase, clazz);
}
@Override
public void init(SQLiteDatabase sqLiteDatabase, Class<T> clazz) {
this.mSqLiteDatabase = sqLiteDatabase;
this.mClazz = clazz;
// 创建表的sql语句
/*create table if not exists Person (id integer primary key autoincrement, name text, age integer, flag boolean)*/
StringBuilder sb = new StringBuilder();
sb.append("create table if not exists ")
.append(DaoUtil.getTableName(mClazz))// 表名为类的名字
.append("(id integer primary key autoincrement, ");// id 为 int的主键 自增长
// 通过反射获取类中的属性
Field[] fields = mClazz.getDeclaredFields();
for (Field field : fields) {//遍历属性
field.setAccessible(true);// 设置权限
String name = field.getName();
String type = field.getType().getSimpleName();// int String boolean
// type需要进行转换 int --> integer, String text;
sb.append(name).append(DaoUtil.getColumnType(type)).append(", ");
}
// 将插入语句的最后两位", "替换为")"
sb.replace(sb.length() - 2, sb.length(), ")");
String createTableSql = sb.toString();
Log.e(TAG, "建表语句--> " + createTableSql);
// 执行建表语句
mSqLiteDatabase.execSQL(createTableSql);
}
// 插入数据库 类型为任意类型
@Override
public long inert(T t) {
/*
通常我们可能直接调用
ContentValues values = new ContentValues();
values.put("name","wz");
values.put("author","xx");
values.put("price",1.0);
db.insert("Book",null,values);
但是这样其实不方便 如果我们删除或增加Book.java的字段 那么这段代码需要修改
并且 对于不同的对象 我们还需要写不同的插入逻辑,利用反射 就可以直接绕开这两个问题
因此 这里我们使用了反射
*/
ContentValues values = contentValuesByObj(t);
return mSqLiteDatabase.insert(DaoUtil.getTableName(mClazz), null, values);
}
@Override
public void deleteAll() {
mSqLiteDatabase.execSQL("DROP TABLE " + DaoUtil.getTableName(mClazz));
//mSqLiteDatabase.execSQL("DELETE FROM " + DaoUtil.getTableName(mClazz));
}
// 查询
// 修改
// 删除
// obj 转成 ContentValues
// ContentValues实际作用类似与hashMap 只不过它的value只能push基本类型
private ContentValues contentValuesByObj(T obj) {
ContentValues contentValues = new ContentValues();
// 通过反射获取mClazz定义的filed(以Person为例 返回的是age 和 name字段)
Field[] fields = mClazz.getDeclaredFields();
for (Field field : fields) {
Method putMethod;
try {
// 设置权限,私有和共有都可以访问
field.setAccessible(true);
// 获取field的名称(如age)
String key = field.getName();
// 获取field的value(如30)
Object value = field.get(obj);
// 虽然使用反射会有一点性能的影响 但是影响很小
// 而且源码里面 activity实例的创建 View创建反射等都使用了反射
// 因此这里也会使用反射 获取put方法
// (如ContentValues.class.getDeclaredMethod("put",String.class, java.lang.Integer))
// 代表希望调用的是put(String key, Integer value)的方法
putMethod = ContentValues.class.getDeclaredMethod("put",
String.class, value.getClass());
// 通过反射执行ContentValues的putXXX方法
// 相当于调用类似 contentValues.put("age",30);
putMethod.invoke(contentValues, key, value);
} catch (Exception e) {
e.printStackTrace();
}
}
return contentValues;
}
}
工厂类
public class DaoSupportFactory {
private static final String TAG = "DaoSupportFactory";
// 单例对象
private static DaoSupportFactory mFactoryInstance;
private final SQLiteDatabase mSqLiteDatabase;
private DaoSupportFactory(Context context) {
// 把数据库放到内存卡里面 TODO 没有判断是否有存储卡 没有动态申请权限
// path:/storage/emulated/0/Android/data/com.example.learneassyjoke/files/nhdz/database
File dbRoot = new File(context.getExternalFilesDir(null)
.getAbsolutePath() + File.separator + "nhdz" + File.separator + "database");
if (!dbRoot.exists()) {
if (!dbRoot.mkdirs()) {
Log.e(TAG, "DaoSupportFactory: 创建db路径失败");
}
;
}
File dbFile = new File(dbRoot, "nhdz.db");
// 打开或者创建一个数据库 并存储数据库操作的引用
Log.e(TAG, "DaoSupportFactory: 创建db路径==>" + dbRoot);
mSqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
}
public static DaoSupportFactory getFactoryInstance(Context context) {
if (mFactoryInstance == null) {
synchronized (DaoSupportFactory.class) {
if (mFactoryInstance == null) {
mFactoryInstance = new DaoSupportFactory(context);
}
}
}
return mFactoryInstance;
}
// 获取DAO对象 用于操作数据库
public <T> IDaoSupport<T> getDao(Class<T> clazz) {
return new DaoSupport<>(mSqLiteDatabase, clazz);
}
}
测试插入数据
IDaoSupport<Person> daoSupport = DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class);
// 最少的知识原则
new Thread(() -> {
long startTime = System.currentTimeMillis();
int totalNum = 1000;
for (int i = 0; i < totalNum; i++) {
daoSupport.inert(new Person("hjcai", i));
}
long endTime = System.currentTimeMillis();
Log.e(TAG, " insert " + totalNum + " cost time -> " + (endTime - startTime));
}).start();
测试删表
IDaoSupport<Person> daoSupport = DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class);
daoSupport.deleteAll();
5.主要调用流程
在activity中调用DaoSupportFactory.getFactoryInstance:触发DaoSupportFactory单例的创建 其构造方法中会在指定目录创建数据库nhdz.db(如果有必要)并获取sqlite的操作引用
接着调用getDao:将之前获取的sqlite传入IDaoSupport的实现类DaoSupport DaoSupport自动调用初始化方法,而后利用反射创建表Person,其中的数据字段均由反射得来
测试方法DaoSupport inert则先通过反射将具体的Person实例转换为ContentValues对象(ContentValues实际上就是一个Map 里面有一个个键值对)然后调用数据库操作的引用的insert方法将数据插入,当然我们也可以使用execSQL来执行插入操作
deleteAll则是直接执行删除语句
6.测试结果
2021-03-17 20:23:59.857 8159-8211/com.example.learneassyjoke E/MyActivity: insert 1000 cost time -> 5206
2021-03-17 20:24:31.277 8227-8251/com.example.learneassyjoke E/MyActivity: insert 1000 cost time -> 5216
2021-03-17 20:24:44.300 8264-8288/com.example.learneassyjoke E/MyActivity: insert 1000 cost time -> 5976
2021-03-17 20:25:00.142 8300-8324/com.example.learneassyjoke E/MyActivity: insert 1000 cost time -> 5776
可以看到平均插入1000条数据耗时5.7s左右,耗时还是比较大的
下一节将讨论如何进行数据库插入的优化