查询 更新 删除的实现
修改IDaoSupport接口部分 删除查询部分 增加返回查询支持类接口 增加删除更新的接口
// 获取专门查询的支持类 按照语句查询
QuerySupport<T> querySupport();
// 按照语句删除
int delete(String whereClause, String... whereArgs);
// 按照语句更新
int update(T obj, String whereClause, String... whereArgs);
DaoSupport是实现部分
删除查询部分 实现上面新增的接口
private QuerySupport<T> mQuerySupport = null;
@Override
public QuerySupport<T> querySupport() {
if (mQuerySupport == null) {
mQuerySupport = new QuerySupport<>(mSqLiteDatabase, mClazz);
}
return mQuerySupport;
}
@Override
public int delete(String whereClause, String[] whereArgs) {
return mSqLiteDatabase.delete(DaoUtil.getTableName(mClazz), whereClause, whereArgs);
}
@Override
public int update(T obj, String whereClause, String... whereArgs) {
ContentValues values = contentValuesByObj(obj);
return mSqLiteDatabase.update(DaoUtil.getTableName(mClazz),
values, whereClause, whereArgs);
}
因为按条件查询 分组 排序等查询的操作稍微复杂 所以单独为查询做了一个支持类 其实也比较简单 主要用的就是builder设计模式
/**
* Created by hjcai on 2021/3/25.
* 专门用来查询的类
* 利用builder设计模式 设置各种参数
*/
public class QuerySupport<T> {
private static final String TAG = "QuerySupport";
// 查询的列
private String[] mQueryColumns;
// 查询的条件
private String mQuerySelection;
// 查询的参数
private String[] mQuerySelectionArgs;
// 查询分组
private String mQueryGroupBy;
// 查询对结果集进行过滤
private String mQueryHaving;
// 查询排序
private String mQueryOrderBy;
// 查询可用于分页
private String mQueryLimit;
private final Class<T> mClazz;
private final SQLiteDatabase mSQLiteDatabase;
public QuerySupport(SQLiteDatabase sqLiteDatabase, Class<T> clazz) {
this.mClazz = clazz;
this.mSQLiteDatabase = sqLiteDatabase;
}
public QuerySupport<T> columns(String... columns) {
this.mQueryColumns = columns;
return this;
}
public QuerySupport<T> selectionArgs(String... selectionArgs) {
this.mQuerySelectionArgs = selectionArgs;
return this;
}
public QuerySupport<T> having(String having) {
this.mQueryHaving = having;
return this;
}
public QuerySupport<T> orderBy(String orderBy) {
this.mQueryOrderBy = orderBy;
return this;
}
public QuerySupport<T> limit(String limit) {
this.mQueryLimit = limit;
return this;
}
public QuerySupport<T> groupBy(String groupBy) {
this.mQueryGroupBy = groupBy;
return this;
}
public QuerySupport<T> selection(String selection) {
this.mQuerySelection = selection;
return this;
}
public List<T> query() {
Cursor cursor = mSQLiteDatabase.query(DaoUtil.getTableName(mClazz), mQueryColumns, mQuerySelection,
mQuerySelectionArgs, mQueryGroupBy, mQueryHaving, mQueryOrderBy, mQueryLimit);
clearQueryParams();
return cursorToListByReflect(cursor);
}
private void clearQueryParams() {
mQueryColumns = null;
mQuerySelection = null;
mQuerySelectionArgs = null;
mQueryGroupBy = null;
mQueryHaving = null;
mQueryOrderBy = null;
mQueryLimit = null;
}
// 多次调用反射 查询所有列表
private List<T> cursorToListByReflect(Cursor cursor) {
List<T> list = new ArrayList<>();
while (cursor.moveToNext()) {
// 通过反射创建T对象 第一次反射 以Person为例 相当于调用Person p = new Person()
T instance = null;// 要调用此方法 必须有无参构造方法
try {
instance = mClazz.newInstance();
// 反射获取T对象所有的属性 第二次反射
Field[] fields = mClazz.getDeclaredFields();
// 遍历field 从数据库取出数据填充到instance
for (Field field : fields) {
Object value = null;
field.setAccessible(true);
// 以Person为例 它有个属性String name, fieldName 则是 name
String fieldName = field.getName();
// 查询当前属性在数据库中所在的列 下面则相当于调用cursor.getColumnIndex("name")
int index = cursor.getColumnIndex(fieldName);
if (index != -1) {
// 根据对象T的属性类型推算cursor的方法 如cursor.getString cursor.getInt
Method cursorGetColumnMethod = convertType2CursorMethod(field.getType());// 在该方法进行第三次反射
// 通过反射获取 value 第四次反射 相当于cursor.getString(cursor.getColumnIndex("name"))//cursor.getColumnIndex("name")在上面已经调用
value = cursorGetColumnMethod.invoke(cursor, index);
if (value == null) {
continue;
}
// 处理一些特殊的部分
if (field.getType() == boolean.class || field.getType() == Boolean.class) {
// sqlite不支持bool类型 使用0代表false 1代表true
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) {
// sqlite不支持char类型 取第0位即可
value = ((String) value).charAt(0);
} else if (field.getType() == Date.class) {
// sqlite不支持Date类型 存储的是时间戳
long date = (Long) value;
if (date <= 0) {
value = null;
} else {
value = new Date(date);
}
}
} else {
Log.e(TAG, "cursorToList: 该属性没有存储在数据库中");
continue;
}
// 反射注入属性的值(以person为例,类似调用person.setName(value))
// 第五次反射
field.set(instance, value);
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
list.add(instance);
}
cursor.close();
Log.e(TAG, "cursorToList: " + list.size());
return list;
}
// 获取cursor的方法
private Method convertType2CursorMethod(Class<?> type) throws NoSuchMethodException {
// 根据数据类型 得到不同cursor方法
String methodName = getColumnMethodName(type);
// 第三次反射 根据方法名和参数类型调用
return Cursor.class.getMethod(methodName, int.class);
}
// 根据数据类型 得到不同cursor方法 如getInt getString
private String getColumnMethodName(Class<?> fieldType) {
String typeName;
if (fieldType.isPrimitive()) { // 如果是基本数据类
// 将int boolean float等转换为对象的形式 即首字母大写
/*
* @see java.lang.Boolean#TYPE
* @see java.lang.Character#TYPE
* @see java.lang.Byte#TYPE
* @see java.lang.Short#TYPE
* @see java.lang.Integer#TYPE
* @see java.lang.Long#TYPE
* @see java.lang.Float#TYPE
* @see java.lang.Double#TYPE
* @see java.lang.Void#TYPE
*/
typeName = DaoUtil.capitalize(fieldType.getName());
} else {
typeName = fieldType.getSimpleName();
}
// 上面获取对象T的Java的get方法,如Integer String Boolean 下面需要转成SQLite里面的数据类型
// 如getBoolen转换为数据库的getInt getChar转换为数据库的getString
String methodName = "get" + typeName;
switch (methodName) {
case "getBoolean":
case "getInteger":
methodName = "getInt";
break;
case "getChar":
case "getCharacter":
methodName = "getString";
break;
case "getDate":
methodName = "getLong";
break;
}
return methodName;
}
}
其实上面主要代码都是从DaoSupport中挪过来的 新增部分主要是builder设计模式。这里的条件查询有很多种 但实际上也不难,只要常用的几个理解就OK 比如group by,having等复杂的条件查询 暂时不理解也没什么问题,一般用到的情况比较少
当然 我认为还可以增加一个接口用于直接调用SQL语句 对于复杂的数据库操作,还是直接执行sql语句比较清楚
以下有个链接可以方便了解数据库的一些语句
https://www.w3school.com.cn/sql/sql_groupby.asp
最后修改和增加几个测试方法
private void deleteByArgs(View view) {
QuerySupport<Person> querySupport = DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).querySupport();
List<Person> persons = querySupport.query();
Log.e(TAG, "删除前所有数据: "+persons);
// 删除所有名字为hjcai的
DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).delete("name =?","hjcai");
Log.e(TAG, "删除后所有数据: "+DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).querySupport().query());
}
private void update(View view) {
new Thread(() -> {
QuerySupport<Person> querySupport = DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).querySupport();
List<Person> persons = querySupport.orderBy("age").query();
Log.e(TAG, "更新前: "+persons);
if ( persons != null && persons.size() >0){
Person person = persons.get(0);
// 把所有name=hjcai的 更新为age最小的样子
DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).update(person,"name = ?","hjcai");
Log.e(TAG, " 更新后 -> " + querySupport.query());
}
}).start();
}
private void queryAll(View view) {
new Thread(() -> {
QuerySupport<Person> querySupport = DaoSupportFactory.getFactoryInstance(MainActivity.this).getDao(Person.class).querySupport();
// List<Person> people = querySupport.selection("name = ? AND age = ?").selectionArgs("hjcai","0").query();
// List<Person> people = querySupport.selection("name = ?").selectionArgs("hjcai").columns("name","age").query();
// List<Person> people = querySupport.selection("name = ?").selectionArgs("hjcai").query();
// List<Person> people = querySupport.selection("name = ?").selectionArgs("hjcai").limit("2").query();
// List<Person> people = querySupport.groupBy("name").query();
// 查询所有
List<Person> people = querySupport.query();
Log.e(TAG, "queryAll: " + people);
}).start();
}
网络结合数据库进行缓存
由于内涵段子的服务器已经不再可用,这里只能说说思路 没法测试了
此次demo在之前的数据库和网络相关的文章的demo基础上修改 分别是
https://blog.csdn.net/u011109881/article/details/114703664
https://blog.csdn.net/u011109881/article/details/114948968
https://blog.csdn.net/u011109881/article/details/115216756
修改点
对IHttpEngine接口 怎加缓存参数
// get请求
void get(boolean needCache, Context context, String url, Map<String, Object> params, EngineCallBack callBack);
// post请求
void post(boolean needCache, Context context, String url, Map<String, Object> params, EngineCallBack callBack);
同样HttpUtils也要加参数
private boolean mNeedCache;
// 是否缓存
public HttpUtils cache(boolean needCache) {
this.mNeedCache = needCache;
return this;
}
private void get(String url, Map<String, Object> params, EngineCallBack callBack) {
mHttpEngine.get(mNeedCache, mContext, url, params, callBack);
}
private void post(String url, Map<String, Object> params, EngineCallBack callBack) {
mHttpEngine.post(mNeedCache,mContext, url, params, callBack);
}
增加缓存数据结构
public class CacheData {
// 请求链接
private String mUrlKey;
// 后台返回数据的Json
private String mResultJson;
public CacheData() {
}
public CacheData(String urlKey, String resultJson) {
this.mUrlKey = urlKey;
this.mResultJson = resultJson;
}
public String getResultJson() {
return mResultJson;
}
}
剩下的就是修改http的get post方法了 这里以get为例
@Override
public void get(boolean needCache, Context context, String urlPara, Map<String, Object> params, EngineCallBack callBack) {
final String finalUrl = HttpUtils.jointParams(urlPara, params);
// 先查询数据库看是否有缓存
final List<CacheData> cacheData = DaoSupportFactory.getFactoryInstance(context).getDao(CacheData.class)
.querySupport().selection("mUrlKey=?").selection(finalUrl).query();
if (cacheData != null && cacheData.size() > 0) {
// 数据库有缓存
CacheData cacheDatum = cacheData.get(0);
Log.e(TAG, "读取到缓存" + cacheDatum);
callBack.onSuccess(cacheDatum.getResultJson());
}
Log.e(TAG, "Get请求路径:" + finalUrl);
Request request = new Request
.Builder()
.url(finalUrl)
.tag(context)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
callBack.onError(e);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
if (response.body() == null) {
Log.e(TAG, "Get返回结果是null");
return;
}
String resultJson = response.body().string();
if (cacheData != null && cacheData.size() > 0) {
CacheData cacheDatum = cacheData.get(0);
if (!TextUtils.isEmpty(resultJson) && cacheDatum != null && resultJson.equals(cacheDatum.getResultJson())) {
// 缓存与网络查询结果相等 直接使用缓存
return;
} else {
if (needCache) {
//更新缓存
DaoSupportFactory.getFactoryInstance(context).getDao(CacheData.class)
.delete("mUrlKey=?", finalUrl);
DaoSupportFactory.getFactoryInstance(context).getDao(CacheData.class)
.insert(new CacheData(finalUrl, resultJson));
}
}
}
callBack.onSuccess(resultJson);
Log.e("Get返回结果:", resultJson);
}
});
}
具体逻辑无非是下图
最后 存入数据库时一些特殊字符可能会存不进去 可以用md5进行加密然后存入数据库,当然取数据时也需要先将url进行加密才能读取到正确的数据。
public static String string2MD5(String inStr) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}
char[] charArray = inStr.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuilder hexValue = new StringBuilder();
for (byte md5Byte : md5Bytes) {
int val = ((int) md5Byte) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}