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来实现对象映射,首先是将服务器数据对象进行分割,将实体对象内不断分割,分离出最小对象,从最小对象开始对应关联关系,确认是一对一,还是一对多,还是多对多。需要使用的时候在从数据库中条件查询对应数据。这部分代码有很多是业务逻辑相关的,比较多,所以这里就不贴了。