用户在服务器web前端增加、修改、删除了数据后会导致客户端的数据与服务器端的数据不一致。为了能够使客户端和服务器端的数据一致,客户端需要同步服务器端的这些操作。主要的步骤为:”服务器端修改数据”、“客户端同步服务器端的数据”、“客户端同步完数据后,反馈给服务器””和“”服务器收到客户端的反馈信息,标记客户端已经同步该数据,不用再同步了”。

1、    服务器端修改数据。
以本产品修改商品为例,服务器端修改数据后,在数据库插入一条同步表记录:

// 更新同步对象的DB表int objSequence = getSequence();List<List<BaseModel>> list = updateSyncDBObject(useSYSTEM, posID, req, bmFromDB.getID(), objSequence, SyncCache.SYNC_Type_C, ec, dbName);getSequence方法返回同步顺序sequence:@Overrideprotected int getSequence() {return aiSequenceSyncBlockForCommodityAndBarcodes.incrementAndGet();}/** 不断自增,标记同步块在客户端同步的顺序。<br />* 客户端收到N个同步块后,先排序,再逐个将这些块写入客户端的DB。<br />* 多线程安全问题:本变量不需要加锁,因为每个ACTION都是互斥运行的。<br />* 缺陷:理论上存在过大溢出的bug。实际上在产品的迭代过程中,不可能会有机会溢出 */protected static AtomicInteger aiSequenceSyncBlockForCommodityAndBarcodes = new AtomicInteger();

updateSyncDBObject方法,vscCreate对象设置了三个属性:

syncData_ID:修改的商品的ID

syncType:同步类型,有C、U、D三种类型,代表创建、修改和删除了数据。

objSequence:序列号,设置同步的顺序。一个商品可能会被修改了多次,所以要设置同步的顺序,值越小,代表越早修改,就应先让客户端同步。

/** 更新DB中的同步对象* * @param validPosID*            必须为有效的POS的ID或BaseAction.INVALID_POS_ID* @param objSequence*            POS端收到N个同步块后,同步此块的顺序 */protected List<List<BaseModel>> updateSyncDBObject(Boolean useSYSTEM, int validPosID, HttpServletRequest req, int iBaseModelID, int objSequence, String syncType, ErrorInfo ec, String dbName) {logger.info("更新DB中的同步对象");BaseSyncCache vscCreate = getSyncCacheModel();vscCreate.setSyncData_ID(iBaseModelID);vscCreate.setSyncType(syncType);vscCreate.setSyncSequence(objSequence);vscCreate.setPosID(validPosID);//DataSourceContextHolder.setDbName(dbName);List<List<BaseModel>> list = getSyncCacheBO().createObjectEx((useSYSTEM ? BaseBO.SYSTEM : getStaffFromSession(req.getSession()).getID()), BaseBO.INVALID_CASE_ID, vscCreate);ec.setErrorCode(getSyncCacheBO().getLastErrorCode());ec.setErrorMessage(getSyncCacheBO().getLastErrorMessage());if (getSyncCacheBO().getLastErrorCode() != EnumErrorCode.EC_NoError) {logger.info("创建DB中的同步对象失败,错误码=" + ec.getErrorCode() + ",错误信息=" + ec.getErrorMessage());return null;} else {logger.info("同步对象的DB表插入的数据为:" + list);}return list;}

调用存储过程,主要sql语句如下(查询是否有之前的’U’型未同步,如果有则删除同步主从表,插入最新的‘U’型同步,同步最新的修改操作即可):

SELECT F_ID INTO iMasterID FROM T_commoditySyncCache WHERE F_SyncData_ID = iSyncData_ID AND F_SyncType = 'U';IF (iMasterID IS NOT NULL) THEN DELETE FROM T_commoditySyncCacheDispatcher WHERE F_SyncCacheID = iMasterID ;DELETE FROM T_commoditySyncCache WHERE F_ID = iMasterID;END IF;     INSERT INTO T_commoditySyncCache (F_SyncData_ID, F_SyncSequence, F_SyncType) VALUES (iSyncData_ID, iSyncSequence, cSyncType);SELECT F_ID, F_SyncData_ID, F_SyncSequence, F_SyncType,F_CreateDatetime FROM T_commoditySyncCache WHERE F_ID = LAST_INSERT_ID();IF iPOSID > 0 THEN -- 网页端创建商品时,iPOSID <= 0INSERT INTO T_CommoditySyncCacheDispatcher (F_SyncCacheID, F_POS_ID) VALUES (LAST_INSERT_ID(), iPOSID);SELECT F_ID, F_SyncCacheID, F_POS_ID,F_CreateDatetime FROM T_CommoditySyncCacheDispatcher WHERE F_ID = LAST_INSERT_ID();END IF;SET iErrorCode := 0;  SET sErrorMsg := '';

在T_commoditySyncCache表中查找是否有F_SyncType = 'U'的记录,叫做U型同步块,U代表update更新,如果不为空,则删除记录,并且删除同步从表T_commoditySyncCacheDispatcher对应的记录。

将F_SyncData_ID、F_SyncSequence、F_SyncType插入到T_commoditySyncCache同步表中。

修改了商品ID为291,在t_commoditySyncCache插入的数据如下:

2、客户端同步服务器端的数据。

由于服务器端对商品ID为291的数据进行了修改,所以客户端需要同步这个修改。

客户端请求服务器端,查询是否有需要同步的数据:

private void retrieveNCommodity() {commoditySQLiteEvent.setEventTypeSQLite(BaseSQLiteEvent.EnumSQLiteEventType.ESET_Commodity_RefreshByServerDataAsync_Done);if (!commodityHttpBO.retrieveNAsync(BaseHttpBO.INVALID_CASE_ID, null)) {log.error("POS机启动时,同步商品失败!");Toast.makeText(this, "POS机启动时,同步商品失败!", Toast.LENGTH_SHORT).show();}
}@Override
protected boolean doRetrieveNAsync(int iUseCaseID, BaseModel bm) {log.info("正在执行CommodityHttpBO的retrieveNAsync,bm=" + (bm == null ? null : bm.toString()));httpEvent.setEventProcessed(false);Request req = new Request.Builder().url(Configuration.HTTP_IP + Commodity.HTTP_Commodity_RETRIEVEN).addHeader(BaseHttpBO.COOKIE, GlobalController.getInstance().getSessionID()).build();HttpRequestUnit hru = new RetrieveNCommodity();hru.setRequest(req);hru.setTimeout(TIME_OUT);hru.setbPostEventToUI(true);httpEvent.setStatus(BaseEvent.EnumEventStatus.EES_Http_ToDo);hru.setEvent(httpEvent);HttpRequestManager.getCache(HttpRequestManager.EnumDomainType.EDT_Communication).pushHttpRequest(hru);log.info("正在请求需要同步的Commodity");return true;
}

服务器端查找商品同步表的所有记录:

final List<BaseModel> vsInfoList = SyncCacheManager.getCache(dbName, getSyncCacheType()).readN(false, false);

调用存储过程,sql语句如下:

SELECT F_ID, F_SyncSequence, F_SyncData_ID, F_SyncType FROM t_Commoditysynccache ORDER BY F_ID DESC;SET iTotalRecord := found_rows();SET iErrorCode := 0;SET sErrorMsg := '';

POS收银机已经同步的数据就不需要再同步:

/** 查询posID是否同步了块blockID <br />* 前提条件:所有同存都已经加载在内存中。 <br />* 因为所有同存一开始就全部加载到了内存中,后来也会不断更新,所以这个前提条件是满足的* * @param blockID*            块的ID* @return */public boolean checkIfPosSyncThisBlock(int blockID, int posID) {boolean bSync = false;System.out.println("checkIfPosSyncThisBlock正在加锁....");lock.readLock().lock();// TODO:// 目前hashtable存储的是syncInfo,无法根据块的ID迅速定位到块及其从表信息。将来重构成去掉syncInfo,将块ID作为hashtable的key。而块内部增加一个成员,其值为从表的指针,通过主表能拿到它全部从表的信息for (Iterator<String> it = getCache().keySet().iterator(); it.hasNext();) {BaseSyncCache syncCache = (BaseSyncCache) getCache().get(it.next());if (syncCache.getID() == blockID) {System.out.println(syncCache.getListSlave1());if (syncCache.getListSlave1() == null) {break;}for (Object o : syncCache.getListSlave1()) {if (((BaseSyncCacheDispatcher) o).getPos_ID() == posID) {bSync = true;break;}}}}lock.readLock().unlock();System.out.println("checkIfPosSyncThisBlock已经解锁....");return bSync;}

根据syncDataID查询出数据库中的商品信息:

BaseModel v = getModel();v.setID(syncCache.getSyncData_ID());BaseModel bmTmp = null;if (!syncCache.getSyncType().equals(SyncCache.SYNC_Type_D)) {logger.info("->非D型同步");DataSourceContextHolder.setDbName(dbName);List<List<BaseModel>> modelR1 = getModelBO().retrieve1ObjectEx(BaseBO.SYSTEM, BaseBO.INVALID_CASE_ID, v); // 系统初始化时需要加载所有同存DB……bmTmp = Commodity.fetchCommodityFromResultSet(modelR1);logger.info("返回的普存对象成功:" + bmTmp);

将所有需要同步的商品信息保存到list集合,返回给客户端:

bmTmp.setSyncDatetime(new Date());bmList.add(bmTmp);

客户端得到要同步的列表,先按sequence对同步数据进行排序:

int maxSyncSequence = 0;
for (int i = 0; i < commodityList.size(); i++) {System.out.println(commodityList.get(i).getSyncSequence());if (commodityList.get(i).getSyncSequence() > maxSyncSequence) {maxSyncSequence = commodityList.get(i).getSyncSequence();}
}//生成一个根据SyncSequence升序排列的List
List<BaseModel> orderCommodityList = new ArrayList<BaseModel>();
for (int i = 0; i < maxSyncSequence; i++) {for (int j = 0; j < commodityList.size(); j++) {if (i + 1 == commodityList.get(j).getSyncSequence()) {orderCommodityList.add(commodityList.get(j));}}
}

将sqlite数据库表的商品一个一个更新:

else if (BasePresenter.SYNC_Type_U.equals(commodityType)) {//假如返回的string1==U,则修改SQLite中对应的数据updateSync(iUseCaseID, bmList.get(i));log.info("服务器返回的U型数据成功同步到本地SQLite中!");bm.setSyncDatetime(new Date(System.currentTimeMillis() + NtpHttpBO.TimeDifference));
bm.setSyncType(BasePresenter.SYNC_Type_U);
dao.getCommodityDao().update((Commodity) bm);

3、客户端同步完数据后,反馈给服务器。

将已经同步好的商品反馈feedback给服务器端,下次就不需要再同步了:

/*** 在feedback之前做,判断是否有需要feedback的数据,若有就feedback,若无,则retrieveN下一个model** @param event             当前sqliteEvent* @param syncIDs* @param feedbackDataCase  当前需要进行feedback的model* @param retrieveNDataCase 下一下需要RetrieveN的model*/
private void beforeFeedback(BaseSQLiteEvent event, String syncIDs, String feedbackDataCase, String retrieveNDataCase) {if (event.getStatus() == BaseEvent.EnumEventStatus.EES_SQLite_DoneApplyServerData) {event.setStatus(BaseEvent.EnumEventStatus.EES_SQLite_ToApplyServerData);List<BaseModel> list = (List<BaseModel>) event.getListMasterTable();//if (list != null) {if (list.size() > 0) {for (int i = 0; i < list.size(); i++) {syncIDs = syncIDs + "," + list.get(i).getID();}syncIDs = syncIDs.substring(1, syncIDs.length());//onFeedback(feedbackDataCase, syncIDs);}} else {onRetrieveN(retrieveNDataCase);}}
}case CommodityData:feedbackCommodity(ids);break;private void feedbackCommodity(String ids) {if (!commodityHttpBO.feedback(ids)) {log.error("POS机启动时,feedback Commodity失败!");}
}@Override
public boolean feedback(String feedbackIDs) {log.info("正在执行CommodityHttpBO的feedback,feedbackIDs=" + feedbackIDs);httpEvent.setEventProcessed(false);Request req = new Request.Builder().url(Configuration.HTTP_IP + Commodity.commodityfeedbackURL_FRONT + feedbackIDs + Commodity.commodityfeedbackURL_BEHIND).addHeader(BaseHttpBO.COOKIE, GlobalController.getInstance().getSessionID()).build();HttpRequestUnit hru = new FeedBack();hru.setRequest(req);hru.setTimeout(TIME_OUT);hru.setbPostEventToUI(true);httpEvent.setStatus(BaseEvent.EnumEventStatus.EES_Http_ToDo);httpEvent.setHttpBO(this);hru.setEvent(httpEvent);HttpRequestManager.getCache(HttpRequestManager.EnumDomainType.EDT_Communication).pushHttpRequest(hru);log.info("通知服务器改POS机已经同步了commodityID:" + feedbackIDs);return true;
}

4、服务器收到客户端的反馈信息,标记客户端已经同步该数据,不用再同步了。

服务器从请求体中获取反馈回来的ID集合ids:

String ids = GetStringFromRequest(req, "sID", String.valueOf(BaseAction.INVALID_ID)).trim();String errorCode = GetStringFromRequest(req, "errorCode", String.valueOf(BaseAction.INVALID_ID)).trim();

排序并删除重复值:

Integer[] iArrID = GeneralUtil.toIntArray(ids);List<Integer> listObjID = GeneralUtil.sortAndDeleteDuplicated(iArrID);

更新同步从表t_commoditysynccachedispatcher:

if (updateSyncCacheAfterPOSSync(useSYSTEM, posID, listObjID, dbName, req) != EnumErrorCode.EC_NoError) {String log = "操作失败:记录POS" + posID + "已经同步了块(对应的对象ID={" + iArrID.toString() + "}),通过更新同存及同存DB";logger.info(log);param.put(BaseAction.JSON_ERROR_KEY, EnumErrorCode.EC_OtherError);param.put(KEY_HTMLTable_Parameter_msg, log);return param;}/** 记录POS已经同步了某些块,通过更新同存及同存DB* * @param iPosID*            同步了块的POS机* @param iArrID*            同步块对应的对象的ID数组(不是同步块ID)* @return */protected EnumErrorCode updateSyncCacheAfterPOSSync(Boolean useSYSTEM, int iPosID, List<Integer> listObjID, String dbName, HttpServletRequest req) {List<BaseModel> listSyncCache = SyncCacheManager.getCache(dbName, getSyncCacheType()).readN(false, false);logger.info("listObjID为:" + listObjID);logger.info("listSyncInfo为:" + listSyncCache);for (BaseModel bm : listSyncCache) {BaseSyncCache syncCache = (BaseSyncCache) bm;for (int iID : listObjID) {if (syncCache.getSyncData_ID() == iID) {// 更新同存DB。在同存DB中记录此块已经被此POS同步BaseSyncCacheDispatcher vscd = getSyncCacheDispatcher();vscd.setPos_ID(iPosID);vscd.setSyncCacheID(syncCache.getID());//DataSourceContextHolder.setDbName(dbName);vscd = (BaseSyncCacheDispatcher) getSyncCacheDispatcherBO().createObject((useSYSTEM ? BaseBO.SYSTEM : getStaffFromSession(req.getSession()).getID()), BaseBO.INVALID_CASE_ID, vscd);if (getSyncCacheDispatcherBO().getLastErrorCode() != EnumErrorCode.EC_NoError) {// ...logger.info("同存DB更新失败,块ID=" + vscd + ", 对象ID=" + iID + ", POS ID=" + iPosID + "\t错误码:" + getSyncCacheDispatcherBO().getLastErrorCode());return getSyncCacheDispatcherBO().getLastErrorCode();}logger.info("同存DB已经成功更新,对象ID=" + iID + ", POS ID=" + iPosID + "。块信息:" + syncCache);}}}return EnumErrorCode.EC_NoError;}

调用存储过程,向同步从表中插入一条记录并返回:

IF EXISTS(SELECT 1 FROM t_Commoditysynccachedispatcher WHERE F_SyncCacheID = iSyncCacheID AND F_POS_ID = iPOS_ID) THENSELECT F_ID, F_SyncCacheID, F_POS_ID FROM t_Commoditysynccachedispatcher WHERE F_SyncCacheID = iSyncCacheID AND F_POS_ID = iPOS_ID;SET iErrorCode := 0;SET sErrorMsg := '';ELSEINSERT INTO t_Commoditysynccachedispatcher (F_SyncCacheID, F_POS_ID) VALUES (iSyncCacheID, iPOS_ID);SELECT F_ID, F_SyncCacheID, F_POS_ID,F_CreateDatetime FROM t_Commoditysynccachedispatcher WHERE F_ID = LAST_INSERT_ID();SET iErrorCode := 0;SET sErrorMsg := '';END IF;

如下图,posID为3的收银机同步好了服务端的一条数据,syncCacheID指向了同步主表的ID:

判断是否所有POS收银机都已经同步完这个操作,如果是,则删除同步表和同步从表,不用再提醒POS收银机同步了:

// 如果所有POS机都已经同步了这个块,那就从同存及同存DB表中都删除这个块的相关信息deleteSyncCacheObjectsSinceAllPOSSync(useSYSTEM, iArrID, dbName, req);/** 删除同存DB和同存。 <br />* 当所有POS机都已经同步了某个块,则应当删除这个块相关的同存DB及同存。* * @param iArrID*            块ID的数组 */protected void deleteSyncCacheObjectsSinceAllPOSSync(Boolean useSYSTEM, Integer[] iArrID, String dbName, HttpServletRequest req) {logger.info("删除同存DB和同存,iArrID=" + iArrID);for (int iID : iArrID) {BaseSyncCache bsc = getSyncCacheModel();bsc.setSyncData_ID(iID);DataSourceContextHolder.setDbName(dbName);getSyncCacheBO().deleteObject((useSYSTEM ? BaseBO.SYSTEM : getStaffFromSession(req.getSession()).getID()), BaseBO.INVALID_CASE_ID, bsc);if (getSyncCacheBO().getLastErrorCode() == EnumErrorCode.EC_NoError) {// 表明所有POS机都已经同步了这个块,应当删除相关的同存BaseModel bm = getModel();bm.setID(iID);SyncCacheManager.getCache(dbName, getSyncCacheType()).delete1(bm);System.out.println("所有POS机都已经同步了这个块" + iID + ",删除了相关的同存。删除块" + iID + "相关同存的错误码=" + getSyncCacheBO().getLastErrorCode());logger.info("所有POS机都已经同步了这个块" + iID + ",删除了相关的同存。删除块" + iID + "相关同存的错误码=" + getSyncCacheBO().getLastErrorCode());} else {System.out.println("并非所有POS机都已经同步了这个块ID=" + iID + ",所以并未删除这个块相关的同存DB数据。");logger.info("并非所有POS机都已经同步了这个块ID=" + iID + ",所以并未删除这个块相关的同存DB数据。");}}}

调用存储过程:

IF (SELECT COUNT(1) FROM t_pos WHERE F_Status = 0) = (SELECT COUNT(1) FROM T_CommoditySyncCacheDispatcher WHERE F_SyncCacheID IN (SELECT F_ID FROM T_CommoditySyncCache WHERE F_SyncData_ID = iSyncData_ID)) THENDELETE FROM T_CommoditySyncCacheDispatcher WHERE F_SyncCacheID IN (SELECT F_ID FROM T_CommoditySyncCache WHERE F_SyncData_ID = iSyncData_ID);DELETE FROM T_CommoditySyncCache WHERE F_SyncData_ID =  iSyncData_ID;SET iErrorCode := 0;SET sErrorMsg := '';ELSE SET iErrorCode := 7;SET sErrorMsg := 'POS机还没全部同步';END IF;

Java实现客户端同步服务器端的数据相关推荐

  1. JAVA学习-JAVA实现客户端与服务器端的TCP通信

    JAVA实现客户端与服务器端的TCP通信 (JAVA 工程训练阶段一.训练任务三基本通信能力.基本任务3.2javaTCP 通信) 编写两个java application 应用程序,完成以下功能: ...

  2. Java实现客户端与服务器端的通信

    客户端与服务器端交互数据需要进行通信,本文介绍安卓客户端是如何与服务器端进行通信的,包括客户端连接服务器端.客户端向服务器端发送请求.将请求信息封装成请求单元.将请求单元存放到队列.从队列中获取请求单 ...

  3. java单客户端和服务器端

    基于TCP协议的网络编程可以实现C/S结构的程序,C客户端发送数据,S服务器端接收数据后发回给客户端,客户端接收后输出到屏幕.客户端通过创建Socket连接服务器,服务器端监听到连接后,也创建Sock ...

  4. Android 客户端与服务器端进行数据交互(一、登录服务器端)

    概要 安卓APP要实现很多功能(比如登录注册.发表评论等)时都必须要使用到网络数据交互.所以在学习了这部分内容后,就将其以最常见的登录过程为例整理出来,也方便跟我一样的新手能迅速学习上手. 预期效果图 ...

  5. Netty服务器部署在Android设备上,接收来自PC客户端的Java Socket客户端发送的JSON数据

    Netty服务器部署在Android设备上,接收来自PC客户端的Java Socket客户端发送的数据 一个简单的模型,在Android手机上部署一个Netty写的服务器,绑定端口9000,等待客户端 ...

  6. Java实现客户端向服务器端定时上传数据

    在客户端用户操作后,先将数据上传到服务器再响应给用户,响应过程可能会比较慢.为了快速响应用户操作,可以先将数据存到客户端中,后面再将数据按一定时间段上传到服务器上.本文将介绍:"定义一个用于 ...

  7. Java实现客户端与服务器端的时间同步

    在客户端获取的当前时间和在服务端获取的当前时间往往会存在差异.有时我们需要知道在客户端创建数据时,相对于服务器的时间是多少.这是我们需要知道客户端和服务端获取当前时间的时间差,从而可以算出相对于服务器 ...

  8. java 实现 web 客户端_Java web客户端和服务器端交互的原理

    Java web客户端和服务器端交互的原理 其实HTTP客户端和服务器端的交互原理很简单:即先是浏览器和服务器端建立Socket无状态连接,也就是短连接,然后通过IO流进行报文信息(这个报文是严格遵循 ...

  9. Java实现简易TCP客户端、服务器端通信程序

    本学期计算机网络课程要求完成一个TCP和一个UDP的通信程序,我完成了功能的简单实现,下面讲讲我的TCP程序的实现.(UDP的见另一篇博客) 目录 效果展示 一.项目结构 二.完整代码 1.TCPCl ...

最新文章

  1. CRNN竞赛程序实现过程
  2. matlab代码重改python代码,对应函数
  3. 在python中 函数赋值给变量时,需要注意的几个事项
  4. Coursera吴恩达《卷积神经网络》课程笔记(3)-- 目标检测
  5. 运行keras出现 FutureWarning: Passing (type, 1) or ‘1type‘ as a synonym of type is deprecated解决办法
  6. MySQL笔记——DQL查询数据
  7. jmeter连接mysql数据库驱动_十八、JMeter实战-JDBC连接MySQL数据库
  8. ASP.NET对验证控件的一些整理(一)
  9. Objective-C学习之旅(四)----内存管理2----retain点语法
  10. tensorflow随笔——简单的卷积神经网络分类实例
  11. linux定时备份Mysql
  12. java 银联,支付宝接口
  13. C# 按层选择 AutoCAD二次开发
  14. 2022年南京医院三基考试耳鼻咽喉科学精选题及答案
  15. 基于python+django框架+Mysql数据库的校园教室实验室预约系统设计与实现
  16. nfc门禁卡的复制和迁移
  17. bwt比对算法 C语言,BWT比对算法
  18. 使用R语言画火山图详细步骤
  19. 小白尝试——去除apk流氓权限
  20. 中文版智能ABC如何移植到英文OS

热门文章

  1. Freemarker日志优化输出
  2. 关于上次微博事件和面试经历的思考
  3. 城市轨道交通体系架构
  4. mybatis的mapper文件中,sql插入语句
  5. mybatis动态插入sql语句的编写
  6. Android:读取Word文档
  7. RH358管理DNS和DNS服务器--自动化名称服务器配置
  8. share一下美团面试经历
  9. 怎么样在excel表格里面每隔一行插入一个空行
  10. Android组件及UI框架大全