反射+jdbc实现orm框架 泛型加多数据源 实现sqlserver迁移pgsql

反射+jdbc实现orm框架 泛型加多数据源 实现sqlserver迁移pgsql,关注同步过程,不在编写代码

在对公司数据库进行热迁移的时候,遇到了很多的问题,针对如下两个大问题分析解决问题。

问题1.需要编写大量类似代码(严重影响编写效率)

在进行数据迁移需要编写大量的类似代码,使用原生jdbc时,编写增删改查业务时, 编写查询的时候,需要指定返回的类型,编写新增修改时 需要指定出每一个字段,就导致需要编写大量的类似代码。为了解决这个问题定义出公共类,写一套orm框架

问题1.解决方案1:

为了避免编写类似的代码,最初的想法是替换一套orm框架,比如jpa ,mybatis-plus ,但是使用别人的orm框架,避免编写大量sql语句,但是会遇到数据源之间切换,可能代码会更加复杂,且别人的orm框架还是要遵守三层规范,虽然基础增删改查不用编写,数据持久层并没有帮我省去,还是要出复制粘贴,对其传输实体类进行修改,一张表进行迁移要写两套dao层,新旧表结构不同。

问题1.解决方案2

根据设计模式,编写一套自己的orm框架,彻底砍掉dao层,定义业务工具类关联dao抽象工具。 不在需要编写任何一行sql语句。 具体代码如下

scss 复制代码
public class PgsqlDaoBase<T> {

    public  final static ConcurrentHashMap<String,String > map = new ConcurrentHashMap<>();
    private String UPDATE="UPDATE";
    private String INSERT="INSERT";

    @Resource(name = SaasBusinessDsConfig.SaasBusinessDbNamedParameterJdbcTemplateName)
    protected NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    protected int[] updateExistingRecords(List<T> existingRecords ,String tableName) {
        Class<?> entityClass = existingRecords.get(0).getClass();
        Field[] fields = entityClass.getDeclaredFields();
        List<String> fieldNames = new ArrayList<>();
        String sql = "";
        if ( map.get(UPDATE +tableName) ==null ||  map.get(UPDATE+tableName).equals("")){
            for (Field field : fields) {
                fieldNames.add(field.getName());
            }
            StringBuilder sb = new StringBuilder();
            sb.append("UPDATE ").append(tableName).append(" SET ");
            for (String fieldName : fieldNames) {
                sb.append(fieldName).append(" = :").append(fieldName).append(", ");
            }
            sb.delete(sb.length() - 2, sb.length()); // 移除最后一个逗号和空格
            sb.append(" WHERE id = :id and accId = :accId");
            sql = sb.toString();
        }else{
            sql=map.get(UPDATE+tableName);
        }
        SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(existingRecords);
        return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
    }


    public int [] insertNewRecords(List<T> newRecords,String name) {
        // 获取实体类的表名
        Class<?> entityClass = newRecords.get(0).getClass();
        // 获取实体类的字段信息
        Field[] fields = entityClass.getDeclaredFields();
        List<String> fieldNames = new ArrayList<>();
        String sql = "";
        if ( map.get(INSERT+name) ==null ||  map.get(INSERT+name).equals("")){

            for (Field field : fields) {
                fieldNames.add(field.getName());
            }
            StringBuilder sb = new StringBuilder();
            sb.append("INSERT INTO ").append(name).append(" (");
            for (String fieldName : fieldNames) {
                sb.append(fieldName).append(", ");
            }
            sb.delete(sb.length() - 2, sb.length());
            sb.append(") VALUES (");
            for (String fieldName : fieldNames) {
                sb.append(":").append(fieldName).append(", ");
            }
            sb.delete(sb.length() - 2, sb.length());
            sb.append(")");
        }else{
            sql=map.get(UPDATE+name);
        }
        // 执行插入操作
        SqlParameterSource[] batchParams = SqlParameterSourceUtils.createBatch(newRecords);
        return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
    }
    public boolean isRecordExists(Integer id ,String name) {
        String sql = "SELECT COUNT(id) FROM "+name+"  WHERE id = :id";
        Map<String, Integer> paramMap = Collections.singletonMap("id", id);
        int count = namedParameterJdbcTemplate.queryForObject(sql, paramMap, Integer.class);
        return count > 0;
    }

    public int deleteBatch(List<Integer> filteredIds,String name) {
        if (filteredIds == null || filteredIds.isEmpty()) {
            return 0;
        }
        String sql = "delete from "+name+" where id in (:ids)";
        SqlParameterSource params = new MapSqlParameterSource("ids", filteredIds);
        return namedParameterJdbcTemplate.update(sql, params);
    }
}

看到这里大家可能会觉得并没有省去持久层,这里只是定义了一个供所有数据库表增删改查的的工具

typescript 复制代码
public abstract class BaseSyncService<K extends HasId, V> extends DataSyncHelper2<V> {

    protected Boolean isLock = true;

    @Autowired
    protected DiscounDaoBase<V> payRecordDaos;

    @Autowired
    protected PageBaseMiddle<K> payClassDao;
    //1.0 计算总页说
    //2.0 将页进行拆分,获取页中的数据List
    //3.0 将查询到的数据转换未目标对象
    //4.0 先创建数据源
    //4.1 再对插入数据源的数据进行编写sql
    public String NAMES;

    public abstract void setTableNAME();

    public String LOCK_KEY = NAMES + "-task-sync-lock";
    public String DATA_KEY = NAMES + "-data-task-sync";

    public String INCREMENT = NAMES + "-INCREMENT";

    public void unSync() {
        RedisLockDao.unlock(LOCK_KEY);
    }

    public void sync(SyncDataDto args, Class<V> type) {
        syncStart(args, Constants.MAX_PAGE_SIZE, type, NAMES);
    }

    public void taskSync(Class<V> type) {
        log.info("准备同步");
        if (!acquireLock(LOCK_KEY)) {
            log.info("同步失败,请清理缓存");
            return;
        }
        try {
            SyncDataDto syncDataDto = new SyncDataDto();
            long startId = syncDataDto.getStartId();
            long endId = syncDataDto.getEndId();
            // 进行同步操作,使用 startId 和 endId 进行同步
            // 更新同步状态
            String jsonStr = RedisDao.get(DATA_KEY);
            startId = JsonUtils.toObject(jsonStr == null ? "{}" : jsonStr, SyncDataDto.class).getStartId();
            endId = startId + Constants.MAX_PAGE_SIZE;

            Long pClassMaxId = payRecordDaos.getMaxId(NAMES);

            while (startId < pClassMaxId) {

                // 存储同步状态到 Redis
                syncDataDto.setStartId(startId);
                syncDataDto.setEndId(endId);

                sync(syncDataDto, type);

                startId += Constants.MAX_PAGE_SIZE;
                endId = startId + Constants.MAX_PAGE_SIZE;

                syncDataDto.setStartId(startId);
                syncDataDto.setEndId(endId);

                RedisDao.set(DATA_KEY, JsonUtils.toJsonStr(syncDataDto));

                log.info("同步进度 start {}  end {}", startId, endId);
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        } finally {
            RedisLockDao.unlock(LOCK_KEY);
        }
    }

    @Override
    public void setVariable(String value) {
        this.KEYVALUE = this.LOCK_KEY;
    }

    @Override
    public void claneCache() {

        clearCache(LOCK_KEY, DATA_KEY, INCREMENT);
    }

    @Override
    protected V fetchById(int Id) {
        return null;
    }

    @Override
    protected int update(V elem) {
        return 0;
    }

    @Override
    protected int removeById(V ele) {
        return 0;
    }

    @Override
    protected int getCount(SyncDataDto syncData) {
        setVariable(LOCK_KEY);
        return payRecordDaos.counts(syncData, NAMES);
    }

    @Override
    protected List<V> getList(SyncDataDto syncData, Class<V> type) {
        return payRecordDaos.getListPage(syncData, NAMES, type);
    }

    @Override
    protected List<K> convertTo(List<V> dataList) {
        ArrayList<K> objects = new ArrayList<>();
        for (V sqlServerEntity : dataList) {
            K entity = convertToMemberEntity(sqlServerEntity);
            if (entity != null) {
                objects.add(entity);
            }
        }
        return objects;
    }

    protected abstract K convertToMemberEntity(V sqlServerEntity);


    //取消增量
    @Override
    public void StopSyncThenSync() {
        isLock = false;
    }

    @Override
    protected int[] insertBatch(List<?> elems, String name) {
        return payClassDao.insertBatch((List<K>) elems, name);
    }

    @Override
    protected int[] insertBatchs(List<?> elems, String name) {
        return payClassDao.insertNewRecords((List<K>) elems, name);
    }

    @Override
    public void SyncThenSync(Class<V> type) {

        if (RedisLockDao.lock(INCREMENT)) {
            try {
                //获取最大的版本号
                while (isLock) {
                    SqlSyncVersionEntity name = sqlSyncVersionDao.getName(INCREMENT);

                    int s = Integer.parseInt(name == null ? "0" : String.valueOf(name.getVersion()));
                    //查询到最大的版本进行保存
                    int maxversion = payRecordDaos.maxVersion(s - 1, NAMES);
                    if (maxversion != s) {
                        List<V> lastChangeVersions = payRecordDaos.getLastChangeVersions(s - 1, NAMES, type);
                        //保存查询到的数据    //一次处理一千条数据
                        //再次进行一次转换
                        List<K> memberEntities = convertTo(lastChangeVersions);
                        payClassDao.insertBatch(memberEntities, NAMES);
                        //删除过的数据永远没有id  只能通过最后一条日志进行删除
                        //删除     新表
                        List<Integer> filteredIds = payRecordDaos.getDeleteIds(s - 1, NAMES);
                        payClassDao.deleteBatch(filteredIds, NAMES);
                        sqlSyncVersionDao.insertandupdateVersions(INCREMENT, maxversion);
                    }
                    Thread.sleep(5 * 60 * 1000);
                }
            } catch (Exception ex) {
                log.error(ex.getMessage(), ex);
            } finally {
                RedisLockDao.unlock(INCREMENT);
            }
        }
        log.error("正圯同步中.........");
    }
}

@Autowired 注入了一个泛型的的dao 这个dao层遇到不同的实体就会进行随意的切换,这里定义的两个抽象方法,需要自己的去实现 实体之间转换的操作 ,你想要把那个字段值的赋值给哪一个字段 ,填充数据库表的抽象 ,

最终成就:完成工具类后 ,就不是关注迁移代码的编写,而是迁移的过程中。

问题2 性能问题:

在上篇我们通过观察ct日志,进行数据同步的时候,几乎每时每刻都在查看ct中是否有数据,准备往新的数据库中同步,在一直监听ct的过程中原表数据在修改,此时一直监听会进行等待,导致查询ct超时,同步功能异常结束。

解决方式: 减少查询的频率,减少系统的压力。增加稳定性,减少资源消耗。

scss 复制代码
public List<T> getListPage(SyncDataDto args, String name ,Class<T> type ) {
    MapSqlParameterSource params = new MapSqlParameterSource();
    StringBuilder wheres = builderWheres(args, params);
    StringBuilder builder = new StringBuilder();
    builder.append(" SELECT * FROM( ")
            .append(" SELECT ROW_NUMBER() OVER(ORDER BY id ) as rowNumber, ")
            .append(" * ")
            .append(" FROM  " +name)
            .append(" WHERE 1=1 ")
            .append(wheres)
            .append(" ) t ")
            .append(" WHERE t.rowNumber >= :bgNumber AND t.rowNumber < :edNumber ");

对查询的条件进行优化,尽量分批查询,且一定要保证数据是顺序查询不丢失。

相关推荐
minstbe4 分钟前
WEB开发 - Flask 入门:Jinja2 模板语法进阶 Python
后端·python·flask
无名之逆31 分钟前
lombok-macros
开发语言·windows·后端·算法·面试·rust·大学期末
m0_748247801 小时前
SpringBoot集成Flowable
java·spring boot·后端
散一世繁华,颠半世琉璃1 小时前
SpringBoot揭秘:URL与HTTP方法如何定位到Controller
spring boot·后端·http
安晴晚风2 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
海绵波波1078 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
网络风云9 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999069 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
运维&陈同学11 小时前
【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
运维·后端·elk·elasticsearch·云原生·自动化·kibana·日志收集
Javatutouhouduan13 小时前
如何系统全面地自学Java语言?
java·后端·程序员·编程·架构师·自学·java八股文