Java实现Mysql数据同步

应用场景:

  • 离线应用程序数据同步到服务器端
  • 服务器端数据同步到离线应用程序

同步记录表设计:

类型不是null主键备注
idint主键id
start_idint  被同步表数据,开始id
end_idint  被同步表数据,结束id
end_upate_timetimestamp  同步结束时的时间(被同步表最后一条同步数据创建时间)
sync_typevarchar  同步类型
create_timetimestamp 创建时间

 

创建同步记录表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。

  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值