• java-数据迁移-定制拓展


    背景

    前文只做了基本的数据迁移功能,然而在实际生产环境下,很可能会因为业务的不同,设计和针对性开发相对应的功能以实现定制性需求;
    事实上,由于是定制性需求,这方面的代码参数可以写死,只对接目标服务。当然也可以编写为配置的方式,但还是那句话,定制!并不见得其他项目就会用到,或者说用到的情况也不多

    定制功能描述

    1.表数据迁移时,还需要将该表的某些字段数据抽取保存到另一张表中

    2.(身份互换)接收者也需要将部分数据迁移到发送者,甚至需要关联查询到数据并保存到发送者,数据迁移后同样需要对某字段进行某种标记

    上代码

    目录结构

    目录结构大同小异,新增两个vo方便参数传递
    在这里插入图片描述

    controller

    存放控制器,对外开放访问接口,尽可能只编写主要逻辑,具体实现交给service层

    service、mapper

    存放服务实现和映射(操作数据库),service负责业务功能的具体实现,需要与数据库交互时在mapper层编写响应的接口

    vo

    存放自定义JavaBean,根据业务需求为了方便参数的传递而创建的自定义JavaBean

    application.yml

    启动配置类,同时也存放数据迁移所需的一些配置,如接收、发送者数据库连接信息和定期任务等

    properties

    存放对配置文件的读取类,使用配置文件保存配置信息,对于使用者来说远比根据需求传入一堆参数更友好

    配置信息

    server:
      port: 9020
    # 配置定时任务执行时间(cron表达式)
    schedules:
      # 目前默认执行频率为每分钟一次
      runBatch: 0 0/1 * * * ?
    # 数据转移参数
    transfer:
      # 发送者数据库地址
      sendUrl: jdbc:mysql://地址:端口/数据库?useUnicode=true&characterEncoding=utf8
      # 发送者数据库用户名
      sendName: 用户名
      # 发送者数据库密码
      sendPassword: 密码
      # 发送者数据库查询语句
      # tableName为表名全称,shortName为表名简称——非必要不用动
      sendSql: select * from tableName where shortName_flag_state != '删除'
      # 接收者数据库地址
      acceptUrl: jdbc:mysql://地址:端口/数据库?useUnicode=true&characterEncoding=utf8
      # 接收者数据库用户名
      acceptName: 用户名
      # 接收者数据库密码
      acceptPassword: 密码
      # 需要修改值的列名
      # shortName为表名简称——非必要不用动
      updateColumn: shortName_flag_state
      # 需要修改为的值
      updateString: 删除
      # 是否开启定时转移任务
      # 只有配置值为 true 时开启定时任务
      schedule: false
    spring:
      application:
        name: transfer         #服务名
      # 接收者數據庫信息
      datasource:
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://地址:端口/数据库?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
        username: 用户名
        password: 密码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.1</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>test</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        
        <dependencies>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-config</artifactId>
                <version>3.1.4.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.2</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    核心代码

    Controller

    import com.rongyi.transfer.service.TransferService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.sql.*;
    
    @Component
    @RestController
    @RequestMapping("/transfer")
    public class TransferController {
        @Autowired
        private TransferService transferService;
    
    	// 迁移
        @GetMapping("/moveTable/{shortName}")
        public String moveTable(@PathVariable("shortName") String shortName) throws SQLException {
        	// c01、c02表需要额外处理
            if (shortName.equals("c01") || shortName.equals("c02")) {
                transferService.moveCustom(shortName);
            // 其他表照常处理
            } else {
                transferService.moveData(shortName);
            }
            return shortName + "迁移完成";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Service

    import com.rongyi.transfer.mapper.TransferMapper;
    import com.rongyi.transfer.vo.InsertVo;
    import com.rongyi.transfer.vo.UpdateVo;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    @Service
    public class TransferService {
        @Resource
        private TransferMapper transferMapper;
    
        @Autowired
        private ApplicationProperties properties;
    
    	// 第一个定制点(其实这部分可能会使用到的项目应该也不少,可以抽取出表名为配置)
    	// 发送者一张表的数据,接收者两张表接收——需要额外新增
        public void extraInsert(InsertVo insertVo) {
            if (insertVo.getTableName().equals("ctlm01")) {
            	// 如果是c01表需要额外新增数据到c11表
                insertVo.setTableName("ctlm11");
                insertVo.setShortName("c11");
            } else { // ctlm02
            	// 如果是c02表需要额外新增数据到c12表
                insertVo.setTableName("ctlm12");
                insertVo.setShortName("c12");
            }
            // 判断表中是否已存在该数据(id判断)
            Integer count = transferMapper.extraCheck(insertVo);
            // 存在则删除
            if (count > 0) {
                transferMapper.extraDelete(insertVo);
            }
            // 新增数据
            transferMapper.extraInsert(insertVo);
        }
    
        public void moveData(String shortName) throws SQLException {
            // 创建变量保存表名
            String tableName = "";
            // 表名简称不为空时,通过表名简称前缀确定表名
            if (shortName == null || "".equals(shortName)) {
                // 表名简称为空直接返回
    //            return "表名不能为空";
                System.out.println("表名不能为空");
            }
            // 表名拼接
            if (shortName.contains("c")) {
                tableName = shortName.replace("c", "ctlm");
            } else if (shortName.contains("i")) {
                tableName = shortName.replace("i", "inv");
            } else if (shortName.contains("w")) {
                tableName = shortName.replace("w", "weight");
            }
            // 表名不为空时才可进行数据转移操作
            if (tableName == null || "".equals(tableName)) {
                // 表名为空说明表名简称前缀无法识别
    //            return "无法识别表";
                System.out.println("无法识别表");
            } else {
                move(tableName, shortName);
            }
            // 正常执行完成返回成功消息
            System.out.println(shortName + "数据转移完毕");
        }
    
    	// 接收到表全名、简称,迁移数据
        private void move(String tableName, String shortName) throws SQLException {
            // 建立发送者连接
            Connection conSend = DriverManager.getConnection(properties.getSendUrl(), properties.getSendName(), properties.getSendPassword());
            Statement stmtSend = conSend.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // 创建变量保存查询sql
            String sql = properties.getSendSql();
            // 拼接
            sql = sql.replace("tableName", tableName);
            sql = sql.replace("shortName", shortName);
            try {
                // 通过拼接好的sql查询数据并保存到结果集
                ResultSet send = stmtSend.executeQuery(sql);
                if (send == null) {
                    // 结果集为空说明没有新数据,不进行后续操作
    //                return "null";
                    System.out.println("没有需要进行迁移的数据");
                    return;
                }
                // 建立接收者连接
                Connection conAccept = DriverManager.getConnection(properties.getAcceptUrl(), properties.getAcceptName(), properties.getAcceptPassword());
                Statement stmtAccept = conAccept.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
                // 定位接收表
                ResultSet accept = stmtAccept.executeQuery("select * from " + tableName);
                // 遍历发送者结果集
                while (send.next()) {
                    // 开启新增
                    accept.moveToInsertRow();
                    // 获取当前表结构
                    ResultSetMetaData metaData = send.getMetaData();
                    // 通过表结构字段数遍历并设置当前行数据
                    for (int i = 1; i <= metaData.getColumnCount(); i++) {
                        accept.updateString(metaData.getColumnLabel(i), send.getString(i));
                    }
                    // 保存新增内容
                    accept.insertRow();
                    // 创建column保存需要修改的字段名
                    String column = properties.getUpdateColumn();
                    column = column.replace("shortName", shortName);
                    // 修改发送者表需要的字段数据
                    send.updateString(column, properties.getUpdateString());
                    send.updateRow();
                }
                // 有异常时抛出
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
    
    	// 第二个定制点:数据源获取数据多表联查获取,发送者(在这里是接收者)比数据源表字段多
    	// 这个处理没有想到很好的代码抽取办法,所以在控制器(controller)层进行了表名判断走不同的方法
    	// 有兴趣的码友可以抽取一下看看,可行希望能告知一声(相互学习!)
        public void moveCustom(String shortName) throws SQLException {
            // 创建变量保存表名
            String tableName = "";
            // 表名简称不为空时,通过表名简称前缀确定表名
            if (shortName == null || "".equals(shortName)) {
                // 表名简称为空直接返回
    //            return "表名不能为空";
                System.out.println("表名不能为空");
            }
            if (shortName.contains("c")) {
                tableName = shortName.replace("c", "ctlm");
            } else if (shortName.contains("i")) {
                tableName = shortName.replace("i", "inv");
            } else if (shortName.contains("w")) {
                tableName = shortName.replace("w", "weight");
            }
            // 表名不为空时才可进行数据转移操作
            if (tableName == null || "".equals(tableName)) {
                // 表名为空说明表名简称前缀无法识别
    //            return "无法识别表";
                System.out.println("无法识别表");
            }
            // 建立发送者连接
            Connection conSend = DriverManager.getConnection(properties.getSendUrl(), properties.getSendName(), properties.getSendPassword());
            Statement stmtSend = conSend.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // 创建变量保存查询sql
            String sql = properties.getSendSql();
            // 拼接
            sql = sql.replace("tableName", tableName);
            sql = sql.replace("shortName", shortName);
            try {
                // 通过拼接好的sql查询数据并保存到结果集
                ResultSet send = stmtSend.executeQuery(sql);
                if (send == null) {
                    // 结果集为空说明没有新数据,不进行后续操作
    //                return "null";
                    System.out.println("没有需要进行迁移的数据");
                }
                // 建立接收者连接
                Connection conAccept = DriverManager.getConnection(properties.getAcceptUrl(), properties.getAcceptName(), properties.getAcceptPassword());
                Statement stmtAccept = conAccept.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
                // 定位接收表
                ResultSet accept = stmtAccept.executeQuery("select * from " + tableName);
                // 遍历发送者结果集
                while (send.next()) {
                    // 开启新增
                    accept.moveToInsertRow();
                    // 获取当前表结构
                    ResultSetMetaData metaData = send.getMetaData();
                    // 通过表结构字段数遍历并设置当前行数据
                    for (int i = 1; i <= metaData.getColumnCount(); i++) {
                        accept.updateString(metaData.getColumnLabel(i), send.getString(i));
                    }
                    // 保存新增内容
                    accept.insertRow();
                    // 创建column保存需要修改的字段名
                    String column = properties.getUpdateColumn();
                    column = column.replace("shortName", shortName);
                    // 修改发送者表需要的字段数据
                    send.updateString(column, properties.getUpdateString());
                    send.updateRow();
                    // 额外添加到表c202或c209
                    if (shortName.equals("c511") || shortName.equals("c512")) {
                        InsertVo insertVo = new InsertVo();
                        insertVo.setTableName(tableName);
                        insertVo.setShortName(shortName);
                        for (int i = 1; i <= metaData.getColumnCount(); i++) {
                            // 获取到相应的两个字段数据
                            if (metaData.getColumnLabel(i).equals(shortName + "_id_supp")) {
                                insertVo.setIdSupp(send.getString(i));
                            }
                            if (metaData.getColumnLabel(i).equals(shortName + "_var_supp")) {
                                insertVo.setVarSupp(send.getString(i));
                            }
                        }
                        extraInsert(insertVo);
                    }
                }
                // 有异常时抛出
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
            // 正常执行完成返回成功消息
            System.out.println(shortName + "数据转移完毕");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204

    Mapper

    我这里mapper使用的注解方式,并且为了通用还使用了${}的方式替换SQL语句
    当然这有一定的风险,可以对传入的shortName先做一个防注入处理

    import com.rongyi.transfer.vo.InsertVo;
    import com.rongyi.transfer.vo.UpdateVo;
    import org.apache.ibatis.annotations.*;
    
    @Mapper
    public interface TransferMapper {
        // c202、c209表额外操作
        @Insert("insert into ${tableName} (${shortName}_id, ${shortName}_id_supp, ${shortName}_var_supp, ${shortName}_date_create, ${shortName}_flag_state) values(#{idSupp}, #{idSupp}, #{varSupp}, sysdate(), '正常')")
        void extraInsert(InsertVo insertVo);
    
        @Select("select count(*) from ${tableName} where ${shortName}_id = #{idSupp}")
        Integer extraCheck(InsertVo insertVo);
    
        @Delete("delete from ${tableName} where ${shortName}_id = #{idSupp}")
        void extraDelete(InsertVo insertVo);
    
        // 反迁移操作原表——配置文件的接收表与当前发送表一致
        @Update("update ${tableName} set ${column} = #{value} where ${shortName}_id = #{id}")
        void extraUpdate(UpdateVo updateVo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    vo

    import lombok.Data;
    import lombok.ToString;
    
    @Data
    @ToString
    public class InsertVo {
        String tableName;
        String shortName;
        String idSupp;
        String varSupp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    import lombok.Data;
    import lombok.ToString;
    
    @Data
    @ToString
    public class UpdateVo {
        String tableName;
        String shortName;
        String id;
        String column;
        String value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    End

    之所以叫定制拓展,是因为并不见得是大众项目所需要用到的,甚至就当前项目(服务)需要用到;
    当然也有一些拓展功能可能是会有不少项目用到,但是又不好抽取通用代码的,我也归类于定制
    码友们如果也有遇到什么有意思或者有困难的定制需求,欢迎一块沟通解决(手动笑脸)

  • 相关阅读:
    人均瑞数系列,瑞数 4 代 JS 逆向分析
    三战MySQL数据库【终极篇】
    【论文阅读】Attention Is All You Need
    Git分支管理
    [PyTorch][chapter 61][强化学习-免模型学习 off-policy]
    ES 中时间日期类型 “yyyy-MM-dd HHmmss” 的完全避坑指南
    【云原生】DevOps(七):Kubernetes编排工具
    柏林噪声算法(Perlin Noise)
    Principled Instructions Are All You Need for Questioning LLaMA-1/2, GPT-3.5/4
    云服务器常见的几种防护方法
  • 原文地址:https://blog.csdn.net/weixin_48415369/article/details/126423827