数据迁移——将数据由SQLServer迁移到PostgreSQL中

文章目录

  • 开发背景
    • 需求背景
    • 分析解决
      • 解决数据库不同源问题
      • 解决数据量过大问题
      • 解决写入比读取慢的问题
      • 解决主键冲突问题
  • 源码
    • 核心依赖
    • Java源码
      • 工具类
      • 通用模块
      • 实体类
      • Feign远程调用
      • SQLServer
      • 数据转换
      • 转换函数调用
      • repository
      • service
      • controller
      • Bootstrap 启动类
    • 配置文件
  • 运行结果

开发背景

需求背景

  由于项目重构需要,现将数据由SQLServer迁移到PostgreSQL中,由于原项目是单工程项目(也就是说工程表中永远只有一条数据),现将多个工程项目迁移合并到一个数据库中,这样工程表中就会出现多个项目(此时id必然会冲突)。

分析解决

解决数据库不同源问题

  由于此时的数据迁移不仅仅是由一个数据库迁移到另一个相同数据库类型的另一个数据库,而是由SQLServer迁移到PostgreSQL中(数据库不同源)。此时我们首先面临的是数据库不同所带来的表结构语句差异性问题。

  而为了解决该问题,我才去如下的解决方案:

  • 采用JPA,通过使用Hibernate的JPA屏蔽数据库底层的差异性,以此来解决两者数据库不同所带来的数据操作问题。
  • 采用读写分离的实现方案,将该实现拆分成两个相互独立的微服务模块,分别是spring-sqlserverspring-postgresql。前者只用于数据的读取(从SQLServer中),后者只用于数据的写入(到PostgreSQL中)。

  备注,这里的spring-sqlserver指的是前一篇文章读取数据表中的文件字节流,因而与spring-sqlserver模块相关的源码直接在前一篇文章中,这里就不再列出。

解决数据量过大问题

  由于需要导入的数据量很大,约有30G,所以我这里采用了与之前相同的分页查询策略,这里我们可以通过配置文件手动设置每次分页查询的信息条数,这里我设置成了100条,也就是说,每次从spring-sqlserver中读取100条信息,通过JPA批量插入到spring-postgresql中,直至将表中所有的信息全部遍历完毕。

解决写入比读取慢的问题

  为了解决写入比读取慢的问题,我这里采用的是FixedThreadPool线程池,通过手动配置线程(测试中使用了10条线程),不过我还是编写了不使用线程池的方法。

  换言之,我这里一共有两套方案,一套是使用线程池,另一套是不使用线程池。

  这里需要注意的是,由于FixedThreadPool的固定缺陷,也就是说假设我将其活跃线程数量设置成2,则当前活跃的线程数量确实是2,但是其线程总数并不为2,而是占满整个线程池。换言之,多余的线程会在线程池内排队,直到等到其执行为止,这里存在一个很大的问题,那就是Feign的调用超时问题,也就是说,当spring-postgresql请求发生后,前面的信息请求能够正常进行,但是后面的请求则会出现连接超时(因为请求在FixedThreadPool线程池中不断等待,直到超时),这会导致后续所有的请求全部都出现请求失败的情况。因而我们需要将Hystrix、ribbon等尽可能关闭,以防信息请求不完整。这里为了安全起见,还是推荐使用非线程池的方式。

解决主键冲突问题

  为了解决主键冲突问题,我这里采用了如下做法:

  • 生成新的主键

  将原有的主键id更改为id_v,并生成新的主键id_k

  • 添加时间戳

  由于数据库的导入是一个一个进行的,因而我们在所有的表中都添加时间字段,记录当前该条信息被导入的时间戳。这样一旦数据插入失败,方便我们定位出错位置。

  • 添加projectId字段

  每个项目在添加时,都在每一张表中添加一个projectId,该projectId也是一个时间戳(Long类型),与之前时间戳不同之处在于,之前的时间戳记录的是每条信息导入的时间,也就是说,单独一个项目数据的导入,每条录入信息的时间戳可能是不同的。而projectId记录的是该项目开始导入的时间戳,一个项目数据库确定,该时间戳在每一张表的每一条数据中都是相同的,以此来区分不同的项目数据库版本。

源码

核心依赖


    
    
        org.springframework.cloud
        spring-cloud-starter-eureka
    
    
    
        org.springframework.cloud
        spring-cloud-starter-ribbon
    
    
    
        org.springframework.cloud
        spring-cloud-starter-hystrix
    
    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
    
        org.postgresql
        postgresql
    
    
    
        org.mybatis.spring.boot
        mybatis-spring-boot-starter
        1.3.1
    
    
    
        org.springframework.cloud
        spring-cloud-starter-feign
    
    
    
        com.squareup.okhttp3
        okhttp
    

Java源码

工具类

  TimeFormatUtil

package com.lyc.postgresql.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/11 16:52
 * @description: 格式化时间字符串
 **/
public class TimeFormatUtil {

    // 开始时间
    private long startTime = 0;
    // 截止时间
    private long endTime = 0;

    /**
     * 创建TimeFormatUtil实例
     * @return
     */
    public static TimeFormatUtil newTimeFormatUtil(){
        return new TimeFormatUtil();
    }

    /**
     * 设置开始时间
     * @return
     */
    public void setStartTime(){
        this.startTime = new Date().getTime();
    }

    /**
     * 设置截止时间
     * @return
     */
    public void setEndTime(){
        this.endTime = new Date().getTime();
    }

    /**
     * 计算程序运行总耗时
     * @return
     */
    public String getSpendTime(){
        // 获取耗时时间
        long spendTime = this.endTime - this.startTime;
        // 将耗时时间转换成时间字符串
        return timeToString(spendTime);
    }

    /**
     * 将long类型的时间转换成时间字符串
     * @param spendTime 消耗时间
     * @return
     */
    private String timeToString(long spendTime) {
        // 毫秒
        long millis = spendTime % 1000;
        // 将用时全部转换成秒
        long secondTemp = spendTime / 1000;
        // 时
        long hour = secondTemp / 3600;
        // 剩余的秒
        secondTemp = secondTemp % 3600;
        // 分
        long minutes = secondTemp / 60;
        // 秒
        long second = secondTemp % 60;
        // 返回计算的最终结果
        return "本次执行耗时:" + hour + "小时" + minutes + "分钟" + second + "秒" + millis + "毫秒";
    }

    /**
     * 将时间字符串格式化成时间对象
     * @param dateString 时间字符串
     * @return
     */
    public String toDate(String dateString) throws ParseException {
        Locale localeUS = new Locale("en","US");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy",localeUS);
        Date date = simpleDateFormat.parse(dateString);
        //2019-03-19 07:14:04
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
    }

}

  FieldFormatUtil

package com.lyc.postgresql.util;

import com.google.common.collect.Maps;
import lombok.var;

import java.util.Date;
import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 11:22
 * @description: Map 类型的数据格式化成对应的格式并返回
 **/
public class FieldFormatUtil {

    private final Map<String,Object> map;
    private final DataConvertUtil dataConvertUtil;


    /**
     * FieldFormatUtil 构造函数
     * @param map
     */
    public FieldFormatUtil(Map<String,Object> map) {
        if(null != map) {   // 如果不为空,则获取数据
            this.map = map;
        } else {  // 否则设置一个默认的空对象
            this.map = Maps.newHashMap();
        }
        dataConvertUtil = DataConvertUtil.newDataConvertUtil();
    }

    /**
     * 创建一个FieldFormatUtil 对象
     * @param map
     * @return
     */
    public static FieldFormatUtil newFieldFormatUtil(Map<String,Object> map){
        return new FieldFormatUtil(map);
    }

    /**
     * 获取整型数据
     * @param k
     * @return
     */
    public int getInt(String k){
        var obj = this.map.get(k);
        return dataConvertUtil.getInt(obj);
    }

    /**
     * 获取Long型数据
     * @param k
     * @return
     */
    public Long getLong(String k) {
        var obj = this.map.get(k);
        return dataConvertUtil.getLong(obj);
    }

    /**
     * 获取String型数据
     * @param k
     * @return
     */
    public String getString(String k) {
        var obj = this.map.get(k);
        return dataConvertUtil.getString(obj);
    }

    /**
     * 获取Double类型的数据
     * @param k
     * @return
     */
    public Double getDouble(String k) {
        var obj = this.map.get(k);
        return dataConvertUtil.getDouble(obj);
    }

    /**
     * 获取时间类型的数据
     * @param k
     * @return
     */
    public Date getDate(String k) {
        var obj = this.map.get(k);
        return dataConvertUtil.getDate(obj);
    }

    /**
     * 获取double类型的数据
     * @param k
     * @return
     */
    public float getFloat(String k) {
        var obj = this.map.get(k);
        return dataConvertUtil.getFloat(obj);
    }

}

  DataConvertUtil

package com.lyc.postgresql.util;

import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;

import java.util.Date;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 11:37
 * @description: 数据类型转换工具
 **/
@Slf4j
@NoArgsConstructor
public class DataConvertUtil {

    /**
     * 创建一个DataConvertUtil对象
     * @return
     */
    public static DataConvertUtil newDataConvertUtil(){
        return new DataConvertUtil();
    }

    /**
     * 将对象转换成字符串
     * @param o
     * @return
     */
    public String getString(Object o){
        String str = String.valueOf(o);
        if("null".equals(str) ){
            return null;
        }
        return String.valueOf(o);
    }

    /**
     * 将对象转换成字符串
     * @param o
     * @return
     */
    public Long getLong(Object o){
        return Long.valueOf(String.valueOf(o));
    }

    /**
     * 将对象转换成int
     * @return
     */
    public int getInt(Object o){
        String str = String.valueOf(o);
        if("null".equals(str) ){
            return 0;
        }
        return Integer.valueOf(str);
    }

    /**
     * 将对象转换成Double类型数据
     * @param o
     * @return
     */
    public double getDouble(Object o){
        return Double.valueOf(String.valueOf(o));
    }

    /**
     * 将对象转换成Date
     * @param obj
     * @return
     */
    public Date getDate(Object obj) {
        if(obj instanceof Long){  // 如果是long类型
            // 则将其转换成Long数据类型
            Long timeLong = Long.valueOf(String.valueOf(obj));
            // 将long类型的数据转换成DateTime数据类型
            DateTime dateTime = new DateTime(timeLong);
            // 将Long类型的数据转换成时间
            return dateTime.toDate();
        } else {  // 否则直接返回一个空值
            return null;
        }
    }

    /**
     * 将对象转换成double
     * @param obj
     * @return
     */
    public float getFloat(Object obj) {
        if(obj != null){
            return Float.valueOf(String.valueOf(obj));
        }
        return 0L;
    }

}

通用模块

  CommonConstant

package com.lyc.postgresql.common;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/16 16:07
 * @description: 系统常量
 **/
public class CommonConstant {

    public interface TableVersion{
        String PROJECT_ID = "projectId";
    }

}

  EntityConvert

package com.lyc.postgresql.common;

import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 11:16
 * @description: 实体类转换父类
 **/
public interface EntityConvert<T> {

    T formatToEntity(Map<String,Object> map);

}

  EntityFunction

package com.lyc.postgresql.common;

import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/30 10:26
 * @description: 实体类函数
 **/
@FunctionalInterface
public interface EntityFunction {

    EntityModel convertToEntity(Map<String,Object> map);

}

  EntityModel

package com.lyc.postgresql.common;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/30 11:17
 * @description: 实体类模型
 **/
public class EntityModel {
}

  EntityRepository

package com.lyc.postgresql.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/30 9:55
 * @description: 此处是数据类与持久化Repository所对应的映射关系
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EntityRepository {

    private String tableName;
    private EntityFunction entityFunction;
    private JpaRepository jpaRepository;

}

  NumberThreads

package com.lyc.postgresql.common;

import lombok.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/2 16:04
 * @description: 线程配置文件
 **/
@Component
@Getter
@Setter
public class NumberThreads {

    // 线程池中最大运行的线程数
    @Value("${thread.numberTreads}")
    private String numberThreads;
    // 每次读取的最大信息条数
    @Value("${pagequery.cycleTime}")
    private String cycleTime;

}

实体类

  GcFilenum

package com.lyc.postgresql.entity;

import com.lyc.postgresql.common.EntityModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/4 16:05
 * @description:  filenum实体类
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class GcFilenum extends EntityModel {

    @Id
    @GeneratedValue
    private Long id_k;
    private String id_v;
    private String mid;
    private String topid;
    private String flag;
    private Integer nlevel;
    private String str;
    private String spstr;
    private String content;
    private Date createTime;
    // 工程id
    private Long projectId;

}

  其它省略

Feign远程调用

  ItemFeignClient

package com.lyc.postgresql.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;
import java.util.Map;

/**
 * Feign 执行远程调用
 */
@FeignClient(value = "sqlserver-server")    //声明这是一个Feign的客户端
public interface ItemFeignClient {

    /**
     * 查询指定数据表信息的总条数
     * @param table
     * @return
     */
    @GetMapping("/countFrom/{table}")
    int countFrom(@PathVariable("table") String table);

    /**
     * 依次遍历数据表中的数据
     * @param table 表名
     * @param sortedField 排序字段
     * @param start 查询开始点
     * @param size 本次查询信息条数
     */
    @GetMapping("/selectListPageQueryFrom/{table}/{sortedField}/{start}/{size}")
    List<Map<String,Object>> selectListPageQuery(@PathVariable("table") String table, @PathVariable("sortedField") String sortedField, @PathVariable("start") int start, @PathVariable("size") int size);

}

SQLServer

  CollectionTables

package com.lyc.postgresql.sqlServer;

import com.google.common.collect.Lists;
import com.lyc.postgresql.sqlServer.tables.Tables;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/29 15:39
 * @description: 收集所有的数据表与排序字段,将其放入到List容器中
 **/
@Slf4j
public class CollectionTables {

    private final List<Tables> tablesList = Lists.newArrayList();

    /**
     * 创建CollectionTables对象
     * @return
     */
    public static CollectionTables newCollectionTables(){
        return new CollectionTables();
    }

    public List<Tables> getTables(){
        for(Tables table : Tables.values()){
            log.info("表名:{},排序字段:{}",table.getTableName(),table.getSortedField());
            tablesList.add(table);
        }
        return tablesList;
    }

}

  Tables

package com.lyc.postgresql.sqlServer.tables;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum Tables {

    INTERFACE("interface","listId")
    ,FILENUM("filenum","mid")
    ...;

    private String tableName;
    private String sortedField;

}

数据转换

  FilenumConvert

package com.lyc.postgresql.targetDataConvert;

import com.lyc.postgresql.common.CommonConstant;
import com.lyc.postgresql.common.EntityConvert;
import com.lyc.postgresql.entity.GcFilenum;
import com.lyc.postgresql.util.FieldFormatUtil;


import java.util.Date;
import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/4 16:18
 * @description: Filenum 对象转实体类
 **/
public class FilenumConvert implements EntityConvert<GcFilenum> {

    @Override
    public GcFilenum formatToEntity(Map<String, Object> map) {

        FieldFormatUtil field = FieldFormatUtil.newFieldFormatUtil(map);
        return GcFilenum.builder()
                .id_v(field.getString(  "id"))
                .mid(field.getString( "mid"))
                .topid(field.getString( "topid"))
                .flag(field.getString("flag"))
                .nlevel(field.getInt( "nlevel"))
                .str(field.getString( "str"))
                .spstr(field.getString( "spstr"))
                .content(field.getString("content"))
                .createTime(new Date())
                .projectId(field.getLong(CommonConstant.TableVersion.PROJECT_ID))
                .build();
    }

    public static FilenumConvert newFilenumConvert() { return new FilenumConvert();}

}

  其它省略。

转换函数调用

  FilenumConvertFunction

package com.lyc.postgresql.convert;

import com.lyc.postgresql.common.EntityFunction;
import com.lyc.postgresql.common.EntityModel;
import com.lyc.postgresql.targetDataConvert.FilenumConvert;

import java.util.Map;

/**
 * @author: zhangzhenyi
 * @date: 2019/4/4 16:14
 * @description: FilenumConvert Function
 **/
public class FilenumConvertFunction implements EntityFunction {

    @Override
    public EntityModel convertToEntity(Map<String, Object> map) {
        FilenumConvert filenumConvert = FilenumConvert.newFilenumConvert();
        return filenumConvert.formatToEntity(map);
    }
    
}

  其它省略。

repository

  FilenumRepository

package com.lyc.postgresql.repository;

import com.lyc.postgresql.entity.GcFilenum;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Filenum Repository
 */
public interface FilenumRepository extends JpaRepository<GcFilenum,Long> {
}

  其它省略。

service

  CollectionRepositoryService

package com.lyc.postgresql.service;

import com.google.common.collect.Lists;
import com.lyc.postgresql.convert.*;
import com.lyc.postgresql.repository.*;
import com.lyc.postgresql.common.EntityFunction;
import com.lyc.postgresql.common.EntityRepository;
import com.lyc.postgresql.sqlServer.tables.Tables;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/30 9:53
 * @description: 收集所有的Repository对象
 **/
@Service
public class CollectionRepositoryService {

    @Autowired
    InterfaceRepository interfaceRepository;
    @Autowired
    FilenumRepository filenumRepository;
    
    //其它省略


    /**
     * 收集所有的Repository对象
     * @return
     */
    public List<EntityRepository> getRepositories(){

        List<EntityRepository> entityRepositoryList = Lists.newArrayList();

        // 添加Interface
        EntityFunction interfaceFunction = new InterfaceConvertFunction();
        EntityRepository interfaceEntityRepository = EntityRepository.builder()
                .tableName(Tables.INTERFACE.getTableName())
                .jpaRepository(interfaceRepository)
                .entityFunction(interfaceFunction)
                .build();

        // 添加filenum
        EntityFunction filenumFunction = new FilenumConvertFunction();
        EntityRepository filenumEntityRepository = EntityRepository.builder()
                .tableName(Tables.FILENUM.getTableName())
                .jpaRepository(filenumRepository)
                .entityFunction(filenumFunction)
                .build();

        //其它省略

        // 添加持久化
        entityRepositoryList.add(interfaceEntityRepository);
        entityRepositoryList.add(filenumEntityRepository);
        
        //其它省略。

        return entityRepositoryList;
    }

}

  CopyCoreService

package com.lyc.postgresql.service;

import com.google.common.collect.Lists;
import com.lyc.postgresql.common.CommonConstant;
import com.lyc.postgresql.common.EntityFunction;
import com.lyc.postgresql.common.EntityModel;
import com.lyc.postgresql.common.EntityRepository;
import com.lyc.postgresql.feign.ItemFeignClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/28 18:55
 * @description: 数据复制到PostgreSQL数据库核心类(利用线程池来实现)
 **/
@SuppressWarnings("ConstantConditions")
@Slf4j
@Service
public class CopyCoreService {

    @Autowired
    CollectionRepositoryService collectionRepositoryService;

    public void copyCore(ExecutorService executorService, ItemFeignClient personFeignClient, String table, String sortedField, int start, int size) {
        executorService.execute(() -> {
            // 创建对象数组,用于批量插入数据
            List<EntityModel> entityModelList = Lists.newArrayList();
            // 执行线程,每次读取两条数据
            List<Map<String,Object>> entityList = personFeignClient.selectListPageQuery(table,sortedField,start,size);
            entityList.forEach(map -> {
                log.info(map.toString());
                // 获取转换后的实体类对象
                EntityModel entityModel = getEntityModel(map);
                // 将实体类对象加入到实体类容器中
                entityModelList.add(entityModel);
            });
            if(entityModelList.size() > 0){  // 如果有数据,执行持久化操作
                // 执行批量插入操作
                choiceRepository(entityModelList,entityList);
            }
        });
    }

    /**
     * 没有采用线程池的方法
     * @param itemFeignClient Feign发起远程调用
     * @param table 表名
     * @param sortedField 排序字段
     * @param start 开始位置
     * @param size 信息条数
     */
    public void copyCore(ItemFeignClient itemFeignClient, String table, String sortedField, int start, int size, Long projectId) {
        // 创建对象数组,用于批量插入数据
        List<EntityModel> entityModelList = Lists.newArrayList();
        // 执行线程,每次读取两条数据
        List<Map<String,Object>> entityList = itemFeignClient.selectListPageQuery(table,sortedField,start,size);
        entityList.forEach(map -> {
            // 给map追加参数projectId
            map.put(CommonConstant.TableVersion.PROJECT_ID,projectId);
            log.info(map.toString());
            // 获取转换后的实体类对象
            EntityModel entityModel = getEntityModel(map);
            // 将实体类对象加入到实体类容器中
            entityModelList.add(entityModel);
        });
        if(entityModelList.size() > 0){  // 如果有数据,执行持久化操作
            // 执行批量插入操作
            choiceRepository(entityModelList,entityList);
        }
    }

    /**
     * 选择对应的仓库,完成批量持久化操作
     * @param entityModelList
     * @param entityList
     */
    @HystrixCommand(
            fallbackMethod = "copyFallback"   // 失败时执行的方法
    )
    private void choiceRepository(List<EntityModel> entityModelList, List<Map<String, Object>> entityList) {
        // 获取数据表的表名
        String tableName = (String) entityList.get(0).get("tableName");
        // 获取全部的EntityRepository对象
        List<EntityRepository> entityRepositoryList = collectionRepositoryService.getRepositories();
        // 循环遍历,查找对应的持久化类,最后完成持久化操作
        entityRepositoryList.forEach(entityRepository -> {
            // 如果是当前实体类,则执行对应的方法。
            if(tableName.equals(entityRepository.getTableName())){
                JpaRepository jpaRepository = entityRepository.getJpaRepository();
                // 执行批量持久化操作
                jpaRepository.save(entityModelList);
            }
        });
    }

    /**
     * 获取转换后的实体类对象
     * @param map
     * @return
     */
    private EntityModel getEntityModel(Map<String,Object> map) {
        // 获取数据表的表名
        String tableName = (String) map.get("tableName");
        // 创建空实体类对象集合
        List<EntityModel> entityList = Lists.newArrayList();
        // 获取全部的EntityRepository对象
        List<EntityRepository> entityRepositoryList = collectionRepositoryService.getRepositories();
        entityRepositoryList.forEach(entityRepository -> {
            // 如果是当前实体类,则执行对应的方法。
            if(tableName.equals(entityRepository.getTableName())){
                // 获取函数,进行数据类型转换
                EntityFunction entityFunction = entityRepository.getEntityFunction();
                EntityModel entityModel = entityFunction.convertToEntity(map);
                entityList.add(entityModel);
            }
        });
        // 获取实体类对象
        return entityList.get(0);
    }

    /**
     * 失败后,在控制台中打印出失败时的信息
     */
    private void copyFallback(ExecutorService executorService, ItemFeignClient ItemFeignClient, String table, String sortedField, int start, int size){
        log.info("-------------------------------------当前数据查询失败!-----------------------------");
        log.info("失败的数据表为:{}",table);
        log.info("-------------------------------------当前数据查询失败!-----------------------------");
    }

}

  PostgreSQLService

package com.lyc.postgresql.service;

import com.lyc.postgresql.common.NumberThreads;
import com.lyc.postgresql.feign.ItemFeignClient;
import com.lyc.postgresql.sqlServer.CollectionTables;
import com.lyc.postgresql.sqlServer.tables.Tables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/28 9:11
 * @description: PostgreSQL Service
 **/
@Slf4j
@Service
public class PostgreSQLService {

    @Autowired
    ItemFeignClient itemFeignClient;
    @Autowired
    CopyCoreService copyCoreService;
    @Autowired
    NumberThreads threads;

    /**
     * 获取信息的总条数
     * @param table
     * @return
     */
    public int countFrom(String table) {
        return itemFeignClient.countFrom(table);
    }

    /**
     * 将数据从SQLServer中导入到PostgreSQL中
     * @param table 数据表的名称
     * @param sortedField 排序字段
     * @param projectId
     * @return
     */
    public boolean copyFrom(String table, String sortedField, Long projectId) {
        // 获取信息总条数
        int total = itemFeignClient.countFrom(table);
        if(total != 0){   // 如果信息条数不等于0,则执行数据查询操作,否则则不会执行
            // 这个是采用的线程池中的方法,该方法现在使用并不合适
            //traversePerson(total,table,sortedField);
            // 去掉线程池
            traverseWithoutThreadPool(total,table,sortedField,projectId);
        }
        // 执行完毕之后,返回本次请求的状态
        return true;
    }

    /**
     * 去掉线程池的遍历
     * @param total 数据总数
     * @param table 表名
     * @param sortedField 排序字段
     * @param projectId
     */
    private void traverseWithoutThreadPool(int total, String table, String sortedField, Long projectId) {
        // 最后一次读取的信息数
        int lastCount = 0;
        // 每次读取的最大信息条数
        int cycleTime = Integer.valueOf(threads.getCycleTime());
        // 线程调用的总次数
        int threadCount = 0;
        // 1、如果当前信息条数小于等于循环周期数,则默认只执行一次
        if(total < cycleTime){
            // 线程调用次数
            threadCount = 1;
        } else {   // 2、如果当前信息条数大于循环周期数
            // 最后一次读取的信息条数
            lastCount = total % cycleTime;
            if(lastCount == 0){   // 2.1、整除情况 执行次数 = 信息总数 / 循环周期数
                threadCount = total / cycleTime;
            } else {  // 2.2、不能整除情况 执行次数 = 信息总数 / 循环周期数 + 1
                threadCount = total / cycleTime + 1;
            }
        }

        for(int i = 1; i <= threadCount; i ++){     // 循环遍历开始执行
            // 信息开始读取的位置
            final int start = (i - 1) * cycleTime;
            // 1、如果threadCount为1
            if(threadCount == 1){
                // 本次读取的信息条数为总条数
                copyCoreService.copyCore(itemFeignClient,table,sortedField,start, total, projectId);
            } else {  // 2、如果threadCount不为1
                if(i < threadCount){ // 2.1、如果不是最后一次循环,则每次都是整周期读取数据
                    copyCoreService.copyCore(itemFeignClient,table,sortedField,start, cycleTime, projectId);
                } else {  // 2.2、如果是最后一次循环,则读取剩余的信息条数
                    final int size = total - start;
                    copyCoreService.copyCore(itemFeignClient,table,sortedField,start, size, projectId);
                }
            }
        }
    }

    /**
     * 完成多表的数据复制操作
     * @return
     */
    public boolean copy() {
        // 从SQLServer中读取所有的数据表
        CollectionTables collectionTables = CollectionTables.newCollectionTables();
        List<Tables> tableList = collectionTables.getTables();
        // 获取当前时间戳作为项目工程id
        final Long projectId = new Date().getTime();
        tableList.forEach(table -> {
            // 将指定表中的数据从SQLServer中导入到PostgreSQL中
            copyFrom(table.getTableName(),table.getSortedField(),projectId);
        });
        return true;
    }

    /**
     * 循环遍历person
     * @param total 信息的总条数
     * @param sortedField 排序字段
     * @param table 表名
     */
    private void traversePerson(int total, String table, String sortedField){
        // 最后一次读取的信息数
        int lastCount = 0;
        // 每次读取的最大信息条数
        int cycleTime = Integer.valueOf(threads.getCycleTime());
        // 当前运行的线程总数
        int numberThreads = Integer.valueOf(threads.getNumberThreads());
        // 线程调用的总次数
        int threadCount = 0;
        // 启用线程池(内含两个线程)以此执行完全部的数据打印操作,要求,数据不重复,不漏掉
        ExecutorService executorService = Executors.newFixedThreadPool(numberThreads);

        // 1、如果当前信息条数小于等于循环周期数,则默认只执行一次
        if(total < cycleTime){
            // 线程调用次数
            threadCount = 1;
        } else {   // 2、如果当前信息条数大于循环周期数
            // 最后一次读取的信息条数
            lastCount = total % cycleTime;
            if(lastCount == 0){   // 2.1、整除情况 执行次数 = 信息总数 / 循环周期数
                threadCount = total / cycleTime;
            } else {  // 2.2、不能整除情况 执行次数 = 信息总数 / 循环周期数 + 1
                threadCount = total / cycleTime + 1;
            }
        }

        for(int i = 1; i <= threadCount; i ++){     // 循环遍历开始执行
            // 信息开始读取的位置
            final int start = (i - 1) * cycleTime;
            // 1、如果threadCount为1
            if(threadCount == 1){
                // 本次读取的信息条数为总条数
                copyCoreService.copyCore(executorService,itemFeignClient,table,sortedField,start, total);
            } else {  // 2、如果threadCount不为1
                if(i < threadCount){ // 2.1、如果不是最后一次循环,则每次都是整周期读取数据
                    copyCoreService.copyCore(executorService,itemFeignClient,table,sortedField,start, cycleTime);
                } else {  // 2.2、如果是最后一次循环,则读取剩余的信息条数
                    final int size = total - start;
                    copyCoreService.copyCore(executorService,itemFeignClient,table,sortedField,start, size);
                }
            }
        }
        executorService.shutdown();
    }

}

controller

  PostgreSQLController

package com.lyc.postgresql.controller;

import com.lyc.postgresql.service.PostgreSQLService;
import com.lyc.postgresql.util.TimeFormatUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/28 9:10
 * @description: PostgreSQL Controller
 **/
@RestController
public class PostgreSQLController {

    @Autowired
    PostgreSQLService postgreSQLService;

    /**
     * 获取信息的总条数
     * @param table
     * @return
     */
    @GetMapping("/countFrom/{table}")
    public int countFrom(@PathVariable("table") String table){
        return postgreSQLService.countFrom(table);
    }

    /**
     * 完成多表的数据复制操作
     * @return
     */
    @GetMapping("/copy")
    public String copy(){
        TimeFormatUtil timeFormatUtil = TimeFormatUtil.newTimeFormatUtil();
        // 设置程序开始时间
        timeFormatUtil.setStartTime();
        // 拷贝
        boolean flag = postgreSQLService.copy();
        // 设置程序截止时间
        timeFormatUtil.setEndTime();
        // 程序耗时
        String spendTime = timeFormatUtil.getSpendTime();
        // 返回执行后的结果
        return flag ? "数据拷贝成功!" + spendTime : "数据拷贝失败!";
    }

}

Bootstrap 启动类

  PostgreSQLApplication

package com.lyc.postgresql;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/26 22:14
 * @description: PostgreSQL Bootstrap 启动类
 **/
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableFeignClients
@EnableHystrix
public class PostgreSQLApplication {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(PostgreSQLApplication.class,args);
    }

}

配置文件

  application.yml

spring:
  application:
    # 服务名称
    name: postgresql-server
  datasource:
    # spring连接数据库驱动
    driverClassName: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/gs
    username: postgres
    password: root
# jpa自动创建不存在的数据表
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      use-new-id-generator-mappings: true
  jackson:
    serialization:
      indent_output: false

# 服务端口号
server:
  port: 8083

eureka:
  instance:
    # 指明使用IP而不是服务名称来注册自身服务。因为Eureka默认是使用域名进行服务注册和解析
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

### mybatis config ###
mybatis:
  type-aliases-package: com.lyc.postgresql.entity

# Hystrix 配置项
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false

ribbon:
  ReadTimeout: 120000
  ConnectTimeout: 60000

  application.properties

# 线程数量设置
thread.numberTreads=10
# 每次数据复制,单个线程所拷贝的最大数据量
pagequery.cycleTime=100

运行结果

  这个的运行比较简单,直接访问以下的路径即可:

http://localhost:8083/copy

  由于数据量比较大,剩下的就是在一旁默默的等待,时不时注视一下控制台,查看项目数据迁移的进度。

你可能感兴趣的:(数据结构)