1. 概述
上节课我们已经手动的搭建了我们自己的数据库框架,有问题的可以先看下我的上篇文章,自己动手搭建数据库框架,而且运行之后没有问题,可以创建数据库成功,并且也可以循环插入多条数据,而我们上节课是以让其插入10数据为例演示的,那么接下来我们就一次性插入5000条数据来测试下二者所需要的时间,当然你也可以插入10000条数据也都是可以的。
2. 实现方式如下
2.1:接口 IDaoSupport如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/6 19:41
* Version 1.0
* Params:
* Description: 接口
*/
public interface IDaoSupport<T> {
/**
* 创建表
*/
void init(SQLiteDatabase sqLiteDatabase , Class<T> clazz) ;
/**
* 插入数据
*/
public long insert(T t) ;
/**
* 批量插入,用于检测性能
*/
public void insert(List<T> datas) ;
/**
* 查询所有
*/
public List<T> query() ;
/**
* 按照语句查询
*/
}
2.2:接口实现类 DaoSupport类如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/6 19:42
* Version 1.0
* Params:
* Description: 接口实现类
*/
public class DaoSupport<T> implements IDaoSupport<T> {
private SQLiteDatabase mSqliteDatabase ;
private Class<T> mClazz ;
private static final Object[] mPutMethodArgs = new Object[2];
private static final Map<String, Method> mPutMethods
= new ArrayMap<>();
public void init(SQLiteDatabase sqLiteDatabase , Class<T> clazz){
this.mSqliteDatabase = sqLiteDatabase ;
this.mClazz = clazz ;
// 创建表 这个是完整的创建表语句
/*"create table if not exists Person ("
+ "id integer primary key autoincrement, "
+ "name text, "
+ "age integer, "
+ "flag boolean)";*/
// 下边是通过 StringBuffer去动态的拼接表语句,目的就是用append拼接出和上边一样一样的 结构
StringBuffer sb = new StringBuffer() ;
sb.append("create table if not exists ") // 如果表不存在
.append(DaoUtil.getTableName(mClazz)) // 通过 class创建 表名
.append("(id integer primary key autoincrement, ") ;
// 获取Person中所有属性,有多少就可以获取多少
// name就代表 Person中所有的 值,比如是 name、age等等;
// type就代表的是该值对应的所有类型,比如是String、int等等;
Field[] fields = mClazz.getDeclaredFields() ;
for (Field field : fields) {
// 设置权限,private、public、protected都可以
field.setAccessible(true);
// 注意:这里是通过反射,取出 Person中的 name text,
// 首先获取 name,然后再获取name对应的类型 text,这里String 就对应的是 text
// 获取的name
String name = field.getName() ;
// 获取的name的类型type(其实就是text类型,在数据库中 text类型就对应的是String)
String type = field.getType().getSimpleName();
// type需要转换 :int -> integer String -> text
sb.append(name).append(DaoUtil.getColumnType(type)).append(", ") ;
}
// 这里是把最后的 ", " 替换成 ")"
sb.replace(sb.length() - 2 , sb.length() ,")") ;
Log.e("TAG" , "表语句 --> "+sb.toString()) ;
// 执行创建表
mSqliteDatabase.execSQL(sb.toString());
}
/**
* 插入数据库,t 是任意对象
*/
@Override
public long insert(T obj) {
/*ContentValues values = new ContentValues();
values.put("name",person.getName());
values.put("age",person.getAge());
values.put("flag",person.getFlag());
db.insert("Person",null,values);*/
// 这里使用的其实还是原生的方式,只是把 obj转成ContentValues
ContentValues values = contentValuesByObj(obj) ;
return mSqliteDatabase.insert(DaoUtil.getTableName(mClazz) , null , values);
}
/**
* 批量插入
*/
@Override
public void insert(List<T> datas) {
// 批量插入采用事务优化
mSqliteDatabase.beginTransaction();
for (T data : datas) {
// 调用单条数据插入
long number = insert(data) ;
// Log.e("TAG" , "number -> " + number) ;
}
mSqliteDatabase.setTransactionSuccessful();
mSqliteDatabase.endTransaction();
}
@Override
public List<T> query() {
Cursor cursor = mSqliteDatabase.query(DaoUtil.getTableName(mClazz), null, null, null, null, null, null);
return cursorToList(cursor) ;
}
/**
* 查询所有数据
*/
private List<T> cursorToList(Cursor cursor) {
List<T> list = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
// 不断的从游标里面获取数据
do {
try {
// 通过反射new对象
T instance = mClazz.newInstance();
Field[] fields = mClazz.getDeclaredFields();
for (Field field : fields) {
// 遍历属性
field.setAccessible(true);
String name = field.getName();
// 获取角标 获取在第几列
int index = cursor.getColumnIndex(name);
if (index == -1) {
continue;
}
// 通过反射获取 游标的方法 field.getType() -> 获取的类型
Method cursorMethod = cursorMethod(field.getType());
if (cursorMethod != null) {
// 通过反射获取了 value
Object value = cursorMethod.invoke(cursor, index);
if (value == null) {
continue;
}
// 处理一些特殊的部分
if (field.getType() == boolean.class || field.getType() == Boolean.class) {
if ("0".equals(String.valueOf(value))) {
value = false;
} else if ("1".equals(String.valueOf(value))) {
value = true;
}
} else if (field.getType() == char.class || field.getType() == Character.class) {
value = ((String) value).charAt(0);
} else if (field.getType() == Date.class) {
long date = (Long) value;
if (date <= 0) {
value = null;
} else {
value = new Date(date);
}
}
// 通过反射注入
field.set(instance, value);
}
}
// 加入集合
list.add(instance);
} catch (Exception e) {
e.printStackTrace();
}
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
// 获取游标的方法
private Method cursorMethod(Class<?> type) throws Exception {
String methodName = getColumnMethodName(type);
// type String getString(index); int getInt; boolean getBoolean
Method method = Cursor.class.getMethod(methodName, int.class);
return method;
}
private String getColumnMethodName(Class<?> fieldType) {
String typeName;
if (fieldType.isPrimitive()) {
typeName = DaoUtil.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;
}
/**
* 把 obj 转成 ContentValues 利用反射
* 反射其实就是获取的是属性、方法等东西
*/
public ContentValues contentValuesByObj(T obj) {
ContentValues values = new ContentValues() ;
// 封装values
Field[] fields = mClazz.getDeclaredFields() ;
for (Field field : fields) {
try {
field.setAccessible(true);
// 这里的key 就指的是获取的是 Person中的 name、age等所有字段,通过for循环有多少拿多少
String key = field.getName() ;
// 获取value
Object value = field.get(obj) ;
mPutMethodArgs[0] = key ;
mPutMethodArgs[1] = value ;
String filedTypeName = field.getType().getName() ;
Method putMethod = mPutMethods.get(filedTypeName) ;
if (putMethod == null){
// 获取put()方法
putMethod = ContentValues.class.getMethod("put", String.class, value.getClass());
mPutMethods.put(filedTypeName , putMethod) ;
}
// 通过反射执行
putMethod.invoke(values , mPutMethodArgs) ;
} catch (Exception e) {
e.printStackTrace();
}finally {
mPutMethodArgs[0] = null ;
mPutMethodArgs[1] = null ;
}
}
return values;
}
/**
* 删除
*/
public int delete(String whereClause, String[] whereArgs) {
return mSqliteDatabase.delete(DaoUtil.getTableName(mClazz), whereClause, whereArgs);
}
/**
* 更新 这些你需要对 最原始的写法比较明了 extends
*/
public int update(T obj, String whereClause, String... whereArgs) {
ContentValues values = contentValuesByObj(obj);
return mSqliteDatabase.update(DaoUtil.getTableName(mClazz),
values, whereClause, whereArgs);
}
// 查询
// 修改
// 删除
}
2.3:DaoSupportFactory工厂代码如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/6 19:44
* Version 1.0
* Params:
* Description: 这是一个工厂
*/
public class DaoSupportFactory {
private SQLiteDatabase mSqliteDatabase ;
private static volatile DaoSupportFactory mFactory ;
/**
* 持有外部数据库的引用
*/
private DaoSupportFactory(){
// 打开或者创建数据库,这个地方是把数据库放到内存卡中
// 这里需要判断:1. 是否有内存卡 2. 6.0需要动态申请权限(可以直接在app刚运行就申请权限)
File dbRoot = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "nhdz"
+ File.separator + "database") ;
// 如果目录不存在,就去创建目录
if (!dbRoot.exists()){
dbRoot.mkdirs() ;
}
File dbFile = new File(dbRoot ,"nhdz.db") ;
// 打开或者创建一个数据库
mSqliteDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile , null) ;
}
/**
* 这里是双重校验,为了防止多个线程同时操作同一个资源
*/
public static DaoSupportFactory getFactory(){
if (mFactory == null){
synchronized (DaoSupportFactory.class){
if (mFactory == null){
mFactory = new DaoSupportFactory() ;
}
}
}
return mFactory ;
}
/**
* 这里是使用 自己写的创建表的init()方法,如果以后使用第三方方便切换
* @param clazz
* @param <T>
* @return
*/
public <T> IDaoSupport<T> getDao(Class<T> clazz){
IDaoSupport<T> daoSupport = new DaoSupport() ;
daoSupport.init(mSqliteDatabase ,clazz);
return daoSupport ;
}
}
2.4. 与第三方数据库 LitePal批量插入数据时间对比
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/6 18:16
* Version 1.0
* Params:
* Description:
*/
public class SecondActivity extends BaseSkinActivity {
@Override
protected void initData() {
/**
* 思路就是:
* 1>:程序一启动,就会调用工厂DaoSupportFactory,然后调用自己的构造方法,然后创建目录和 nhdz.db数据库 到内存卡中,然后创建 mSliteDataBase数据库;
* 2>:然后调用 init()方法,该方法是在 DaoSupport中实现 init()方法
*
* 目的就是:
* 1>:利用工厂 getFactory(),别人在调用的时候只需要传递 一个 需要存储的 对象就ok,达到最少知识原则的思想
* 2>:别人需要添加、删除、修改、查询都非常方便
*
*
*/
// Person是要 存储的数据
IDaoSupport<Person> daoSupport = DaoSupportFactory.getFactory().getDao(Person.class) ;
// 最少知识原则
// 这里可以直接插入数据到数据库,就不用 Person person = new Person() ;
// daoSupport.insert(new Person("novate" , 26)) ;
/*List<Person> persons = new ArrayList<>() ;
for (int i = 0; i < 5000; i++) {
persons.add(new Person("Novate" , 26 + i)) ;
}
long startTime = System.currentTimeMillis() ;
// 自己的批量插入
daoSupport.insert(persons);
// litepal的批量插入
// DataSupport.saveAll(persons);
long endTime = System.currentTimeMillis() ;
// 这里批量插入1000条数据:目的是为了测试自己写的数据库框架所需要时间和第三方litepal数据库比较
// 统一批量插入5000条:
// 自己的:48426ms 优化后:2285ms 768ms
// litepal:4831ms 优化后:1127ms 1120ms
Log.e("TAG" , "时间差 --> " + (endTime - startTime)) ;*/
List<Person> personSize = daoSupport.query();
Log.e("TAG" , "personSize --> " + personSize.size()) ; // personSize --> 37010
}
@Override
protected void initView() {
//初始化权限
initPermission();
}
@Override
protected void initTitle() {
}
/**
* 初始化权限事件
*/
private void initPermission() {
//检查权限
String[] permissions = CheckPermissionUtils.checkPermission(this);
if (permissions.length == 0) {
//权限都申请了
//是否登录
} else {
//申请权限
ActivityCompat.requestPermissions(this, permissions, 100);
}
}
@Override
protected void setContentView() {
setContentView(R.layout.activity_second);
}
}
经过多次测试,可以看出:
1>:我们自己的数据库批量插入5000条数据所需时间48426ms,采用事务优化后时间是:2285ms 768ms;
2>:litepal数据库时间分别是:4831ms 优化后:1127ms 1120ms;
可以看出我们自己写的数据库批量插入5000条数据相对来说所需时间比litepal稍微快一点,
3>:最下边打印一下:查询数据库中所有的数据,总共有37010条数据;
生成的数据库在我们手机的外部存储卡中,是nhdz/database/nhdz.db文件。
使用NavicatePreminm打开nhdz.db文件就ok。
具体代码已上传至github:
https://github.com/shuai999/EssayJoke_day_10.git