ORM理解
面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
应用场景
首先提出这个解决方案是项目所需,先说一下,我们现在做的项目的应用场景。
该项目是一款点餐pad应用,需求是商户的餐桌数据和菜单数据,更新频率不高,而且餐厅上百道菜和桌子数据比较多,在服务器数据库中涉及到许多的关联表,对应表中要有所有商户菜单,倘若每次进入都从服务器加载菜单数据,服务器将会频繁查询无用的信息,对服务器也是徒增压力。基于此,本地缓存方案是必须的,接下来就是如何建立一套可行的本地数据库缓存方案。思路是在本地建立和服务器一样的表,但是Sqlite要实现同样的Mysql服务器表也是有很多问题,多表关联(一对一,一对多,多对多)这些的实现,以我自己之前维护整理的DBhelper,来实现是很大的工作量ps(其实就是搞不定O(∩_∩)O~),最终选择了sqlite的orm框架--“LitePal”,网络通信一直是用的Volley,数据格式是json,所以我一直用gson解析。
流程梳理
那么思路就很明确了,服务器接口从数据库查询菜单-->Volley通信-->gson根据接口文档解析生成对应的实体类-->业务逻辑实体类操作转化为LitePal数据库表对应对象保存数据-->业务逻辑实体类读取本地数据库菜单-->点菜时加载显示。菜单更新的话应用每次启动后,向服务器查询,本店的菜品最后一次更新时间,与本地保存的更新
时间对比,如果有变化,则后台更新本地数据库缓存。
要点处理
Volley,Gson,Litepal要一起在业务逻辑实体类中处理相关问题,有2点是很重要的
1.接口文档解析实体类:
要生成这个类,首先我们需要根据对应接口,生成一个实体类,当然前提是接口返回的是json串,xml的话,我们这边项目暂时没有用到,所以我没有处理进来。对于这种情况,我这边是直接自定义了一个泛型实体类ResultMapRequest<T> extends Request<T>,对Volley有所了解的话,都知道Volley的相关用法,可以自定义请求请求,并把对于处理封装进去。StringRequest、JsonRequest、ImageRequest等。其中StringRequest用于请求一条普通的文本数据,JsonRequest(JsonObjectRequest、JsonArrayRequest)用于请求一条JSON格式的数据,ImageRequest则是用于请求网络上的一张图片。XMLRequest解析处理服务器返回为xml文件的。他们全部都是继承自Request<T>,而我也是在其中做了一些处理,使其能够兼容Gson,解析处理复杂数据,包括泛型,集合的复杂组合。只要传入对应数据集的Type即可。
package com.asiainfo.orderdishes.http.volley;
import android.util.Log;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.toolbox.HttpHeaderParser;
import com.google.gson.Gson;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
/**
* @author gjr
* 直接将服务器返回内容,转换为对应实体类抛出
* @param <T>对应接口文档建立的实体类
*/
public class ResultMapRequest<T> extends Request<T> {
private final Listener<T> mListener;
private Gson mGson;
private Type modelClass;
public ResultMapRequest(int method, String url, Type model, Listener<T> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mGson = new Gson();
modelClass = model;
mListener = listener;
}
public ResultMapRequest(String url, Type model, Listener<T> listener,
ErrorListener errorListener) {
this(Method.GET, url, model, listener, errorListener);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
Log.d("VolleyLogTag", "result : " + jsonString);
//关键在此处,将服务器内容解析为String字符串,之后再根据对应接口文档生成的实体类的modelClass,生成对应对象
T result = mGson.fromJson(jsonString, modelClass);
return Response.success(result,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
}
那么在新建请求的时候就可以如下面这样调用:
/**
* 获取当前商家的所有菜品信息,并且本地缓存
*/
public void VolleyQueryAllDishes() {
String param = "/appController/queryAllDishesInfoByMerchantId.do?childMerchantId=" + baseApp.getLoginInfo().getChildMerchantId();
Type type = new TypeToken<ResultMap<queryAllDishesByMerchantIdData>>() {
}.getType();
Log.i("Volley", "url:" + Constants.address + param);
ResultMapRequest<ResultMap<queryAllDishesByMerchantIdData>> resultMapRequest = new ResultMapRequest<ResultMap<queryAllDishesByMerchantIdData>>(
Constants.address + param,
type,
new Response.Listener<ResultMap<queryAllDishesByMerchantIdData>>() {
@Override
public void onResponse(
ResultMap<queryAllDishesByMerchantIdData> response) {
switch (Integer.valueOf(response.getErrcode())) {
case 0:
response.getData().getDishes();
BackLogin updateDishesData = new BackLogin();
updateDishesData.setType(2);
updateDishesData.setDbEntity(dbEntity);
updateDishesData.setDishes(response.getData().getDishes());
updateDishesData.setInfo(response.getData().getInfo());
EventBus.getDefault().post(updateDishesData);
baseApp.setLoadDishes(false);
break;
default:
showShortTip(response.getMsg() + "!");
dismissLoadingDialog();
break;
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(VolleyLogTag,
"VolleyError:" + error.getMessage(), error);
dismissLoadingDialog();
showShortTip("网络或服务器异常,自动更新菜单失败!");
}
});
requestQueue.add(resultMapRequest);
resultMapRequest.setRetryPolicy(new DefaultRetryPolicy(10 * 1000, 1,
1.0f));
}
其中的ResultMap<queryAllDishesByMerchantIdData>就是根据queryAllDishesInfoByMerchantId.do接口的接口文档建立的实体类,由于我们接口返回数据最外层是固定的,errcode错误码,msg提示信息,data请求查询成功则返回
queryAllDishesByMerchantIdData,否则返回空。
package com.asiainfo.orderdishes.entity.volley;
/**
* ClassName: ResultMap 网络响应实体类模板
* date: 2015年3月28日 下午15:36
*
* @author gjr
* @param <T> 不同响应返回不同内容
*/
public class ResultMap<T> {
private String errcode;
private String msg;
private T data;
public void setErrcode(String errcode) {
this.errcode = errcode;
}
public String getErrcode() {
return this.errcode;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return this.msg;
}
public void setError(Errors error) {
this.errcode = error.getCode();
this.msg = error.getMsg();
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
至此,第一步完成,从服务器获取数据,通过Volley+Gson的结合,接口文档对应的实体类。
注意:如果开发过程中,接口文档有变,要对应修改实体类,否则数据解析必然抛异常。上面返回成功后通过EventBus向后台线程发送数据,更新本地数据库。
2.建立本地数据库表,利用LitePal来实现对象映射,首先是将服务器数据对象进行分割,将实体对象内不断分割,分离出最小对象,从最小对象开始对应关联关系,确认是一对一,还是一对多,还是多对多。需要使用的时候在从数据库中条件查询对应数据。这部分代码有很多是业务逻辑相关的,比较多,所以这里就不贴了。