Java實現Mysql數據同步

應用場景:

  • 離線應用程序數據同步到服務器端
  • 服務器端數據同步到離線應用程序

同步記錄表設計:

類型 不是null 主鍵 備註
id int 主鍵id
start_id int     被同步表數據,開始id
end_id int     被同步表數據,結束id
end_upate_time timestamp     同步結束時的時間(被同步表最後一條同步數據創建時間)
sync_type varchar     同步類型
create_time timestamp   創建時間

 

創建同步記錄表sql文件:

CREATE TABLE `sync_record` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `start_id` int(11) DEFAULT NULL COMMENT '被同步表數據,開始id',
  `end_id` int(11) DEFAULT NULL COMMENT '被同步表數據,結束id',
  `end_upate_time` timestamp(4) NULL DEFAULT NULL COMMENT '同步結束時的時間(被同步表最後一條同步數據創建時間)',
  `sync_type` varchar(3) DEFAULT NULL COMMENT '同步類型',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='同步記錄表';

本篇博客介紹的是Java程序實現Mysql數據同步,要對抽象類有深刻的理解,不然會對代碼邏輯很懵懂,不懂得同學可以看我這篇博客回憶一下Java基礎知識:

Java抽象類

編寫同步數據邏輯抽象類代碼:

  • 根據主鍵id同步

 AbstractSyncByIdService.java(抽象類)

@Service
public abstract class AbstractSyncByIdService {

    private static final Logger logger = LoggerFactory.getLogger(AbstractSyncByIdService.class);

    @Autowired
    private SyncDao syncDao;


    /**
     * 獲取同步的上一個id
     *
     * @author HeLiu
     * @date 2018/7/18 11:20
     */
    public Integer queryPreviousId(String syncType) {
        return syncDao.queryPreviousId(syncType);
    }

    /**
     * 異常或者結束時,保存或者更新本次的同步記錄
     *
     * @author HeLiu
     * @date 2018/7/18 11:39
     */
    protected void saveOrUpdateSyncRecord(Integer startId, Integer endId, String syncType) {
        boolean exsitFlag = syncDao.queryExsitBySyncType(syncType);
        //如果存在該同步類型的 同步記錄,則只更新endId ; 不存在,則插入該類型的同步記錄
        if (exsitFlag) {
            syncDao.updateEndIdBySyncType(syncType, endId);
        } else {
            syncDao.saveSyncRecord(syncType, startId, endId);
        }
    }

    /**
     * 執行同步,同步中的業務邏輯,數據之間的同步先後關係,都在這裏編寫
     *
     * @author HeLiu
     * @date 2018/7/18 11:36
     */
    public void excuteSync(String syncType, String pcId) {

        logger.info(".......start .excuteSync  ..syncType:{}..", EnumSyncType.enumOfByCode(syncType).desc);

        // 獲取開始id
        Integer previousId = queryPreviousId(syncType);

        // 每次都會執行方法,判斷是否有需要同步的數據
        Pair<Boolean, Object> resultPair = exsitNeedSync(previousId, pcId);
        while (resultPair.getLeft()) {
            // 設置最已同步的id 爲前一個id
            Integer syncEndId = previousId;
            try {
                // 同步數據,並返回結束時,本次同步數據的id,
                // 沒有異常,則表示同步成功
                syncEndId = syncData(resultPair.getRight());
                logger.info(".......同步數據id:{}.. .excuteSync  ..syncType:{}..", syncEndId, syncType);
                // 同步成功 最新同步成功的id , 則變成上一個id
                previousId = syncEndId;

                resultPair = exsitNeedSync(previousId, pcId);
            } catch (Exception e) {
                logger.info(".excuteSync..excetption..previousId:{}...syncType.:{}", previousId, syncType);
                logger.error("excuteSync..excetption.", e);

            } finally {
                // 保存同步記錄,
                // 每次同步成功一條數據,都需要更新最新已同步的id
                logger.info("..saveOrUpdateSyncRecord...........");
                saveOrUpdateSyncRecord(previousId, syncEndId, syncType);
            }
        }
        logger.info(".......end .excuteSync  ..syncType:{}..", EnumSyncType.enumOfByCode(syncType).desc);


    }

    /**
     * 根據同步開始id,同步數據, 返回結束時的id, 不同模塊,實現不一樣,這裏抽象出來
     *
     * @author HeLiu
     * @date 2018/7/18 11:32
     */
    protected abstract Integer syncData(Object data) throws  Exception;

    /**
     * 根據同步id ,查詢是否有需要同步的數據,true 表示有, false 表示沒有
     *
     * @author HeLiu
     * @date 2018/7/18 16:21
     */
    public abstract Pair<Boolean, Object> exsitNeedSync(Integer previousId, String pcId);


}
  •  根據創建時間同步

  AbstractSyncByTimeService.java(抽象類)

@Service
public abstract class AbstractSyncByTimeService {

    private static final Logger logger = LoggerFactory.getLogger(AbstractSyncByTimeService.class);

    @Autowired
    private SyncDao syncDao;

    /**
     * 獲取最後一次的更新時間
     *
     * @author HeLiu
     * @date 2018/7/18 11:20
     */
    public String queryPreviousEndUpdateTime(String syncType) {
        return syncDao.queryPreviousEndUpdateTime(syncType);
    }

    /**
     * 異常或者結束時,保存或者更新本次的同步記錄
     *
     * @author HeLiu
     * @date 2018/7/18 11:39
     */
    protected void saveOrUpdateSyncRecord(String endUpdateTime, String syncType) {
        boolean exsitFlag = syncDao.queryExsitBySyncType(syncType);
        // 如果存在該同步類型的 同步記錄,則只更新同步數據的創建時間; 不存在,則插入該類型的同步記錄
        if (exsitFlag) {
            syncDao.updateEndUpdateTimeBySyncType(syncType, endUpdateTime);
        } else {
            syncDao.saveEndUpdateTimeBySyncType(syncType, endUpdateTime);
        }
    }

    /**
     * 執行同步,同步中的業務邏輯,數據之間的同步先後關係,都在這裏編寫
     *
     * @author HeLiu
     * @date 2018/7/18 11:36
     */
    public void excuteSync(String syncType, String pcId) {

        logger.info(".......start .excuteSync  ..syncType:{}..", EnumSyncType.enumOfByCode(syncType).desc);

        // 獲取開始同步時間
        String endUpdateTime = queryPreviousEndUpdateTime(syncType);

        // 每次都會執行方法,判斷是否有需要同步的數據
        Pair<Boolean, Object> resultPair = exsitNeedSync(endUpdateTime, pcId);
        while (resultPair.getLeft()) {
            // 設置已同步的時間 爲前一個時間
            String syncEndUpdateTime = endUpdateTime;
            try {
                // 同步數據,並返回結束時,本次同步數據的創建時間,
                // 沒有異常,則表示同步成功
                syncEndUpdateTime = syncData(resultPair.getRight());
                logger.info(".......同步數據endUpdateTime:{}.. .excuteSync  ..syncType:{}..", syncEndUpdateTime, syncType);
                // 同步成功 最新同步成功的創建時間 , 則變成上一個創建時間
                endUpdateTime = syncEndUpdateTime;

                resultPair = exsitNeedSync(endUpdateTime, pcId);
            } catch (Exception e) {
                logger.info(".excuteSync..excetption..previousId:{}...syncType.:{}", endUpdateTime, EnumSyncType.enumOfByCode(syncType).desc);
                logger.error("excuteSync..excetption.", e);

            } finally {
                // 保存同步記錄,
                // 每次同步成功一條數據,都需要更新最新已同步的創建時間
                saveOrUpdateSyncRecord(endUpdateTime, syncType);
            }
        }
        logger.info(".......end .excuteSync  ..syncType:{}..", EnumSyncType.enumOfByCode(syncType).desc);


    }

    /**
     * 根據同步開始時間,同步數據, 返回結束時的時間, 不同模塊,實現不一樣,這裏抽象出來
     *
     * @author HeLiu
     * @date 2018/7/18 11:32
     */
    protected abstract String syncData(Object data) throws  Exception;

    /**
     * 根據同步開始時間 ,查詢是否有需要同步的數據,true 表示有, false 表示沒有
     *
     * @author HeLiu
     * @date 2018/7/18 16:21
     */
    public abstract Pair<Boolean, Object> exsitNeedSync(String endUpdateTime, String pcId);


}

 注意:

  • 一個根據兩者同步邏輯都是一樣的主鍵id,前提是你的主鍵id是數字遞增類型的不是UUID之類的,另一個根據數據的創建時間,利用時間有先後的原理。這二者同步的區別要區分好。
  • 根據你同步數據設置好區分的類別也就是syncType,例如:人員-'1';視頻-'2'......,怎麼開心怎麼來。
  • 然後編寫你自己的同步數據邏輯層一定要繼承該類(AbstractSyncByIdService / AbstractSyncByTimeService,重寫抽象類裏面的方法,自定義你自己的業務代碼,因爲不同的同步數據,業務的代碼不一樣。
  • 這兩個抽象類一定要仔細看,有詳細的註解。
  • 兩個抽象方法至關重要,一定要理解這兩個方法的用處。

代碼補充: 

 SyncDao.java

@Repository
public class SyncDao {

    private static final String  name_space = "syncRecord" + SPOT;

    @Autowired
    private DaoClient daoClient;


    /**
     *  根據同步類型,查詢出,原數據表中,開始同步的id
     * @date 2018/7/18 14:18
     */
    public Integer queryPreviousId(String syncType){
        String sqlId = name_space + "queryPreviousId";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        return daoClient.queryForObject(sqlId, param, Integer.class);
    }

    /**
     *  判斷該種類型的同步信息是否存在
     * @author liuao
     * @date 2018/7/18 15:16
     */
    public boolean queryExsitBySyncType(String syncType){
        String sqlId = name_space + "queryExsitBySyncType";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        int count =  daoClient.queryForObject(sqlId, param, Integer.class);
        return count > 0 ? true : false ;
    }

    /**
     *  根據同步類型更新同步結束時的id
     * @author liuao
     * @date 2018/7/18 15:24
     */
    public int updateEndIdBySyncType(String syncType, Integer endId){
        String sqlId = name_space + "updateEndIdBySyncType";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        param.put("endId", endId);
        return daoClient.excute(sqlId, param);
    }

    /**
     *  根據同步類型更新同步結束時的id
     * @author liuao
     * @date 2018/7/18 15:24
     */
    public int updateEndUpdateTimeBySyncType(String syncType, String endUpdateTime){
        String sqlId = name_space + "updateEndUpdateTimeBySyncType";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        param.put("endUpdateTime", endUpdateTime);
        return daoClient.excute(sqlId, param);
    }

    /**
     *  根據同步類型保存同步結束時的更新時間
     * @author liuao
     * @date 2018/7/18 15:24
     */
    public int saveEndUpdateTimeBySyncType(String syncType, String endUpdateTime){
        String sqlId = name_space + "saveEndUpdateTimeBySyncType";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        param.put("endUpdateTime", endUpdateTime);
        return daoClient.insertAndGetId(sqlId, param);
    }


    /**
     *  保存同步記錄
     * @date 2018/7/18 15:28
     */
    public int saveSyncRecord(String syncType, Integer startId ,Integer endId){
        String sqlId = name_space + "saveSyncRecord";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        param.put("startId", startId);
        param.put("endId", endId);
        return daoClient.excute(sqlId, param);
    }

    /**
     *  查詢出最後一次的更新時間
     * @date 2018/8/2 19:48
     */
    public String queryPreviousEndUpdateTime(String syncType) {
        String sqlId = name_space + "queryPreviousEndUpdateTime";
        Map<String,Object> param = new HashMap<>();
        param.put("syncType", syncType);
        return daoClient.queryForObject(sqlId, param, String.class);
    }
}

sql語句:

<sqltemplate id="queryPreviousId">
        <![CDATA[
             SELECT
                IFNULL (MAX(end_id),0) lastId
            FROM SYNC_RECORD
            WHERE SYNC_TYPE = :syncType
		]]>
    </sqltemplate>


    <sqltemplate id="queryExsitBySyncType">
        <![CDATA[
             SELECT
                count(id)
            FROM SYNC_RECORD
            WHERE SYNC_TYPE = :syncType
		]]>
    </sqltemplate>

    <sqltemplate id="updateEndIdBySyncType">
        <![CDATA[
            UPDATE SYNC_RECORD
            SET
                END_ID = :endId
            WHERE SYNC_TYPE = :syncType
		]]>
    </sqltemplate>


    <sqltemplate id="saveSyncRecord">
        <![CDATA[
                INSERT INTO SYNC_RECORD
                SET
                    START_ID = :startId ,
                    END_ID = :endId ,
                    SYNC_TYPE = :syncType

		]]>
    </sqltemplate>

    <sqltemplate id="updateEndUpdateTimeBySyncType">
        <![CDATA[
                update  SYNC_RECORD
                SET
                    end_upate_time = :endUpdateTime
                    where SYNC_TYPE = :syncType

		]]>
    </sqltemplate>


    <sqltemplate id="saveEndUpdateTimeBySyncType">
        <![CDATA[
                INSERT INTO SYNC_RECORD
                SET
                    END_UPATE_TIME = :endUpdateTime ,
                    SYNC_TYPE = :syncType
		]]>
    </sqltemplate>

    <sqltemplate id="queryPreviousEndUpdateTime">
        <![CDATA[
             SELECT
                IFNULL (MAX(end_upate_time),'2018-01-01 00:00:00') lastId
            FROM SYNC_RECORD
            WHERE SYNC_TYPE = :syncType
		]]>
    </sqltemplate>

注意:代碼是死的人是活的,靈活使用,不要被代碼侷限了,這個只是提供一下思路,具體怎麼使用可以自己根據實際需求改和優化,深刻理解設計思路和對抽象類的一個靈活使用。

這裏的Dao層和sql的寫法是jdbctemplate的封裝,可以借鑑我的一篇博客——Java基於jdbctemplate數據持久層操作封裝

剛開始肯定有點難理解,耐下心仔細看,歡迎相互討論——QQ:892715310,WX:Miss5202468。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章