# 数据迁移 AP数据库建表语句如下: ```sql CREATE TABLE IF NOT EXISTS vd.software_dict ( software_name varchar(256) , process_name varchar(128) NOT NULL, software_type int, os_type int, active int, create_time timestamp DEFAULT now(), update_time timestamp DEFAULT now(), PRIMARY KEY (process_name) ); ``` pg数据库建表语句如下: ```sql CREATE TABLE IF NOT EXISTS vd.software_dict ( id int NOT NULL DEFAULT nextval('vd.software_dict_id_seq'), software_name varchar(256) , process_name varchar(128) NOT NULL, software_type int, os_type int, active int, recent_usage int, create_time timestamp DEFAULT now(), update_time timestamp DEFAULT NULL, PRIMARY KEY (id) ) ; ``` ## 任务描述 请使用Java实现数据迁移。完成将PG数据库数据迁移至AP数据库 要求实现必要的函数及mybatis 文件
时间: 2025-03-19 07:15:22 浏览: 30
<think>嗯,用户需要我帮忙用Java实现从PG数据库迁移数据到AP数据库,还涉及到MyBatis的配置和必要的函数。首先,我需要仔细看一下两个数据库的表结构有什么不同。
首先看AP数据库的software_dict表,主键是process_name,字段有software_name、process_name、software_type、os_type、active、create_time、update_time。而PG数据库的同名表多了一个id字段作为主键,还有recent_usage字段,update_time的默认值也不同,AP是now(),PG是NULL。
那数据迁移的时候,需要注意几点:PG里的id字段在AP里没有,所以迁移的时候要忽略掉。另外,PG的recent_usage字段在AP表里不存在,所以这个字段的数据也不能迁移过去。然后AP的update_time默认是now(),但PG的是NULL,可能需要处理一下这个字段的值。如果PG中的update_time为NULL,可能需要在迁移时设置为当前时间,或者保留原值?
接下来,用户要求使用Java和MyBatis,所以可能需要写Mapper接口,定义查询和插入的方法。PG作为源数据库,AP作为目标数据库,所以需要配置两个数据源,或者两个SqlSessionFactory,分别连接两个库。
步骤应该是这样的:首先从PG数据库读取数据,然后转换数据,排除不需要的字段,处理可能的默认值问题,然后写入到AP数据库中。需要注意的是,AP表的主键是process_name,所以在插入的时候可能会遇到主键冲突的情况,比如重复的process_name。这时候可能需要处理冲突,比如覆盖已有记录或者跳过,用户没说,可能需要默认覆盖,或者根据业务需求处理。
所以,首先需要配置两个数据源,PG和AP各自的配置。然后在MyBatis的Mapper里,分别定义查询PG数据和插入AP数据的方法。可能需要一个Service类来协调数据迁移的过程,比如分页查询,避免一次性加载过多数据,特别是数据量大的时候。
在转换数据对象的时候,PG对应的实体类会有id和recent_usage字段,而AP的实体类没有这些字段。所以可能需要两个不同的实体类,或者在同一个实体类中忽略这些字段。或者在查询的时候,SQL语句中不选择这些字段,但用户提供的建表语句显示PG的表有这些字段,所以可能需要排除掉。
例如,查询PG的时候,SQL语句应该是SELECT software_name, process_name, software_type, os_type, active, create_time, update_time FROM vd.software_dict,这样不选择id和recent_usage。或者实体类中包含这些字段,但在插入AP的时候忽略它们。
可能还需要处理字段的映射问题,比如字段名是否一致,但看起来两个表的字段除了id和recent_usage,其他都一样,所以可以直接映射。
然后,MyBatis的插入语句需要对应AP的表结构,排除id和recent_usage,同时处理update_time。如果PG中的update_time是NULL,那么在插入AP的时候,可能使用当前时间,或者保留原NULL,但AP表的update_time默认是now(),所以如果插入时没有提供该字段,会自动设置为当前时间。但根据AP的建表语句,update_time的默认是now(),所以在插入的时候,如果PG中的update_time有值,就使用它,否则使用默认值。这时候在插入语句中是否需要显式设置update_time呢?
比如,如果PG中的update_time是NULL,那么在插入AP时,不设置该字段,让数据库自动填充now()。但如果PG中的update_time有值,则需要使用该值。因此,在插入语句中,应该包含update_time字段,并且使用从PG中获取的值,如果为NULL,则可能由数据库处理,或者设置为当前时间。需要看AP的字段是否允许NULL,根据建表语句,AP的update_time的默认是now(),但字段是否允许NULL?原建表语句中没有NOT NULL约束,所以可能允许NULL。但根据DEFAULT now(),当插入时不指定该字段时,会用默认值。所以,如果从PG中取出的update_time是NULL,那么在插入AP时,不设置该字段,让数据库自动填充当前时间。或者显式地设置为当前时间?
这里可能需要明确处理:在数据迁移时,如果PG的update_time为NULL,则在插入AP时,不设置该字段,让数据库使用默认值now()。如果PG的update_time有值,则使用该值。
所以在转换的时候,需要将PG的update_time值传递给AP的插入语句,如果存在的话。否则,不传递,让数据库处理。
接下来,关于MyBatis的配置,需要两个不同的数据源,每个Mapper接口对应不同的数据库。例如,PgSoftwareDictMapper和ApSoftwareDictMapper,分别使用不同的SqlSessionFactory。
然后,数据迁移的服务类中,可能需要分页查询PG的数据,逐批处理,避免内存溢出。比如,每次查询1000条,处理完再查下一页。
另外,需要考虑事务的问题,比如在插入AP的时候,是否要批量提交,或者在出错时回滚。根据需求,可能需要保证数据迁移的原子性,但如果是大数据量,可能采用分批提交的方式。
现在,关于具体的实现步骤:
1. 配置两个数据源,PG和AP,使用Spring Boot的配置属性。
2. 创建两个配置类,分别生成对应的SqlSessionFactory和DataSource,使用@Configuration和@MapperScan指定不同的包路径或具体的Mapper接口。
3. 定义实体类SoftwareDict,可能包含所有字段,或者在两个不同的实体类中处理。不过为了简化,可能使用一个类,但忽略不需要的字段,比如在插入的时候。
不过,由于PG和AP的表结构不同,可能需要两个不同的实体类,或者在查询时排除某些字段。例如,PgSoftwareDict包含id和recent_usage,而ApSoftwareDict不包含这些字段。
或者,在查询PG的时候,SQL语句中不选择id和recent_usage,这样返回的实体类可以不包含这两个字段。例如:
在PgSoftwareDictMapper.xml中,写查询语句:
SELECT software_name, process_name, software_type, os_type, active, create_time, update_time FROM vd.software_dict
这样,结果映射到ApSoftwareDict的实体类,或者共用的实体类,其中没有id和recent_usage字段。
但这样的话,可能需要两个不同的实体类,或者在查询时使用别名或者忽略某些字段。或者,在实体类中保留这两个字段,但在插入AP时忽略它们。
假设使用同一个实体类,包含所有字段,但在插入AP时,忽略id和recent_usage。
所以,在插入AP的SQL语句中,不包含这两个字段,这样即使实体类中有这些字段的值,插入时也不会被使用。
例如,插入语句:
INSERT INTO vd.software_dict (software_name, process_name, software_type, os_type, active, create_time, update_time) VALUES (#{softwareName}, #{processName}, #{softwareType}, #{osType}, #{active}, #{createTime}, #{updateTime})
这样,即使实体类中有id和recent_usage,也不会被插入到AP的表中。
所以,可能可以共用一个实体类,或者为两个不同的表使用不同的实体类。
接下来,具体的代码实现:
- 实体类SoftwareDict,包含所有字段,包括id和recent_usage,但在AP的插入中不处理这些字段。
或者,创建两个实体类,如PgSoftwareDict和ApSoftwareDict,后者没有id和recent_usage字段。这样更清晰,但需要转换。
在数据迁移时,从PG查询得到PgSoftwareDict对象,转换为ApSoftwareDict对象,排除id和recent_usage,然后插入到AP。
转换过程可以在Service层处理,遍历每个PgSoftwareDict对象,创建对应的ApSoftwareDict对象,复制相应的字段值。
这样更安全,避免字段污染,特别是在字段名或类型不同的情况下。
所以,可能需要两个实体类:
public class PgSoftwareDict {
private Integer id;
private String softwareName;
private String processName;
private Integer softwareType;
private Integer osType;
private Integer active;
private Integer recentUsage; // PG特有
private Date createTime;
private Date updateTime;
// getters and setters
}
public class ApSoftwareDict {
private String softwareName;
private String processName;
private Integer softwareType;
private Integer osType;
private Integer active;
private Date createTime;
private Date updateTime;
// getters and setters
}
然后在Service中将PgSoftwareDict转换为ApSoftwareDict,复制对应的字段,忽略id和recentUsage。
接下来,MyBatis的Mapper接口:
对于PG:
@Mapper
public interface PgSoftwareDictMapper {
List<PgSoftwareDict> selectAll();
// 或者分页查询,比如使用RowBounds或者参数传递offset和limit
List<PgSoftwareDict> selectPage(@Param("offset") int offset, @Param("limit") int limit);
}
对于AP:
@Mapper
public interface ApSoftwareDictMapper {
void insert(ApSoftwareDict softwareDict);
// 或者批量插入
void batchInsert(List<ApSoftwareDict> list);
}
然后,在Service中,实现迁移逻辑:
@Service
@RequiredArgsConstructor
public class DataMigrationService {
private final PgSoftwareDictMapper pgMapper;
private final ApSoftwareDictMapper apMapper;
@Transactional // 可能需要事务管理,根据需求决定
public void migrate() {
int pageSize = 1000;
int offset = 0;
List<PgSoftwareDict> pgList;
do {
pgList = pgMapper.selectPage(offset, pageSize);
if (pgList.isEmpty()) {
break;
}
List<ApSoftwareDict> apList = pgList.stream()
.map(this::convertToAp)
.collect(Collectors.toList());
apMapper.batchInsert(apList);
offset += pageSize;
} while (!pgList.isEmpty());
}
private ApSoftwareDict convertToAp(PgSoftwareDict pg) {
ApSoftwareDict ap = new ApSoftwareDict();
ap.setSoftwareName(pg.getSoftwareName());
ap.setProcessName(pg.getProcessName());
ap.setSoftwareType(pg.getSoftwareType());
ap.setOsType(pg.getOsType());
ap.setActive(pg.getActive());
ap.setCreateTime(pg.getCreateTime());
ap.setUpdateTime(pg.getUpdateTime()); // 如果pg的update_time为null,插入时AP的update_time会用默认值now()吗?
return ap;
}
}
这里需要注意的是,当pg的update_time为null时,插入到AP的update_time字段是否会触发默认值。根据AP的建表语句,update_time的默认是now(),但只有在插入时没有提供该字段的值时才会生效。如果在插入时显式地设置为null,那么AP的该字段会存储null,而不是默认值。所以需要处理这种情况。
因此,在转换时,如果pg.getUpdateTime()为null,可能需要不设置该字段,让AP使用默认值。但在MyBatis中,如果实体类的updateTime为null,插入语句中的update_time字段会被设置为null,导致AP表中的该字段为null,而不是默认值。因此,需要调整插入的SQL语句,只在update_time有值的时候才插入,否则让数据库使用默认值。
修改插入语句,使用动态SQL。例如,在ApSoftwareDictMapper.xml中:
<insert id="insert" parameterType="ApSoftwareDict">
INSERT INTO vd.software_dict (
software_name,
process_name,
software_type,
os_type,
active,
create_time,
<if test="updateTime != null">
update_time
</if>
)
VALUES (
#{softwareName},
#{processName},
#{softwareType},
#{osType},
#{active},
#{createTime},
<if test="updateTime != null">
#{updateTime}
</if>
)
</insert>
不过,这样的写法在字段数量变化时可能有问题,因为当updateTime为null时,INSERT语句中的列和值的数量不一致。因此,可能需要另一种方式,比如使用COALESCE或者设置默认值,或者在插入时不指定update_time字段,这样数据库会自动填充默认值。
所以,正确的做法是,在插入时不指定update_time字段,当pg中的update_time存在时,才插入该值,否则让数据库处理。但如何实现呢?
或者,在插入语句中,总是包含update_time字段,但如果pg中的值为null,则不设置,使用数据库的默认值。但MyBatis中如何做到这一点?
例如,在插入语句中,使用:
INSERT INTO vd.software_dict (software_name, process_name, ..., update_time)
VALUES (#{softwareName}, ..., #{updateTime,jdbcType=TIMESTAMP})
在这种情况下,如果updateTime为null,AP数据库的update_time字段会被设置为null,而不是默认值。因此,这不符合需求。
因此,正确的处理方式是在转换时,如果pg的updateTime为null,则不设置update_time字段,让数据库使用默认值。但如何在MyBatis中实现动态插入?
可以使用动态SQL,当updateTime为null时,不插入该字段,这样数据库就会应用默认值。例如:
<insert id="insert" parameterType="ApSoftwareDict">
INSERT INTO vd.software_dict (
software_name,
process_name,
software_type,
os_type,
active,
create_time
<if test="updateTime != null">
,update_time
</if>
)
VALUES (
#{softwareName},
#{processName},
#{softwareType},
#{osType},
#{active},
#{createTime}
<if test="updateTime != null">
,#{updateTime}
</if>
)
</insert>
这样,当updateTime不为null时,插入该值;否则,不插入该字段,此时AP数据库会用默认值now()填充。这应该可以达到预期效果。
因此,在convertToAp方法中,即使pg的updateTime为null,也会传递给ApSoftwareDict,但在插入时,MyBatis的动态SQL会判断是否为null,从而决定是否包含该字段。
或者,在convertToAp方法中,当pg的updateTime为null时,不设置ApSoftwareDict的updateTime,这样在插入时,该字段为null,动态SQL就会忽略该字段,让数据库使用默认值。不过,如果实体类的updateTime字段为null,动态SQL中的判断会生效,从而不插入该字段,对吗?
是的。例如,如果pg.getUpdateTime()是null,那么转换后的ApSoftwareDict的updateTime也是null。在插入时,MyBatis的<if test="updateTime != null">条件不成立,因此update_time字段不会被包含在INSERT语句中,这样数据库会自动填充默认值now()。这正是我们需要的。
所以,这样的处理是可行的。
接下来,关于主键冲突的问题。AP表的主键是process_name,而PG表的主键是id,所以可能出现多个PG记录具有相同的process_name,这样在插入AP时会发生主键冲突,导致失败。因此,需要处理这种情况,例如,在插入时使用ON CONFLICT (process_name) DO UPDATE,或者在迁移前清空AP表,或者跳过重复记录。
用户的需求中没有说明如何处理冲突,但根据常规做法,可能需要根据业务需求决定。例如,是否覆盖已有记录,或者跳过。如果用户希望AP表中的数据是最新的,可能需要使用MERGE或UPSERT操作。
因此,在插入AP时,可以使用ON CONFLICT处理冲突。例如,在AP的插入语句中,添加冲突处理:
INSERT INTO vd.software_dict (...) VALUES (...) ON CONFLICT (process_name) DO UPDATE SET software_name=EXCLUDED.software_name, ... ;
不过,这会增加SQL的复杂度,并且需要根据业务需求来决定是否更新现有记录。或者,如果确定数据迁移是一次性的,可以在迁移前清空AP表,或者在迁移过程中使用TRUNCATE或DELETE,但用户可能希望保留AP表中的现有数据,只做增量迁移。
如果用户希望覆盖重复的process_name记录,那么需要在插入时使用ON CONFLICT DO UPDATE。如果希望跳过,则使用ON CONFLICT DO NOTHING。
假设用户希望覆盖已有记录,那么修改插入语句:
在ApSoftwareDictMapper.xml中:
<insert id="insert" parameterType="ApSoftwareDict">
INSERT INTO vd.software_dict (
software_name,
process_name,
software_type,
os_type,
active,
create_time
<if test="updateTime != null">
,update_time
</if>
)
VALUES (
#{softwareName},
#{processName},
#{softwareType},
#{osType},
#{active},
#{createTime}
<if test="updateTime != null">
,#{updateTime}
</if>
)
ON CONFLICT (process_name)
DO UPDATE SET
software_name = EXCLUDED.software_name,
software_type = EXCLUDED.software_type,
os_type = EXCLUDED.os_type,
active = EXCLUDED.active,
create_time = EXCLUDED.create_time,
update_time = EXCLUDED.update_time
</insert>
这样,当有冲突时,会更新其他字段的值。但需要注意,如果业务上是否需要保留原create_time?例如,可能create_time应该保留第一次插入的时间,而update_time设置为当前时间。但根据AP表的定义,create_time没有默认值,所以在冲突时是否需要更新它?这需要根据业务逻辑决定。例如,可能冲突时不应该更新create_time,只更新其他字段和update_time。
在这种情况下,可能需要调整ON CONFLICT的SET部分,例如:
ON CONFLICT (process_name)
DO UPDATE SET
software_name = EXCLUDED.software_name,
software_type = EXCLUDED.software_type,
os_type = EXCLUDED.os_type,
active = EXCLUDED.active,
update_time = NOW()
这里,将update_time设置为当前时间,而保留原来的create_time。但原AP表的update_time默认是now(),但这里在冲突时显式设置为NOW(),覆盖原有的update_time。
这样的处理可能更符合业务需求,即在数据迁移时,如果记录已存在,则更新其他字段,并将update_time设置为当前时间。
但具体如何处理需要根据用户需求调整,这里可能需要与用户确认。但根据问题描述,用户没有特别说明,所以可能需要默认使用覆盖的方式,或者在迁移前清空目标表。
如果迁移是全量迁移,并且希望AP表的数据完全替换为PG的数据,可能更简单的方式是先清空AP表,然后再插入所有数据。但用户的任务描述中是否允许这样做?如果AP表在迁移时可能有其他数据,就不能这样做。如果允许清空,可以在迁移前执行TRUNCATE。
例如,在DataMigrationService中,添加一个truncate方法:
在ApSoftwareDictMapper中:
@Mapper
public interface ApSoftwareDictMapper {
void truncate();
// 其他方法
}
对应的XML:
<delete id="truncate">
TRUNCATE TABLE vd.software_dict
</delete>
然后在migrate方法中调用truncate(),确保插入前表是空的,避免主键冲突。但如果AP表中已经有数据需要保留,则不能这样做。所以需要用户明确这一点。如果用户需要增量迁移,并处理冲突,就需要使用ON CONFLICT语句。
假设用户希望全量迁移,每次迁移前清空AP表,那么代码可以修改为:
在migrate方法中:
apMapper.truncate();
然后进行数据迁移。这样可以避免主键冲突的问题。
但用户的需求中没有说明这一点,所以需要假设如何处理。这里可能需要提供一个可配置的选项,或者根据情况选择。但作为示例代码,可能更倾向于使用清空表的方式,或者使用ON CONFLICT来更新。
总结步骤:
1. 配置双数据源,使用MyBatis分别连接PG和AP数据库。
2. 定义对应的实体类和Mapper接口,处理字段差异。
3. 在Service中实现分页查询和批量插入,处理字段转换和冲突问题。
现在,关于MyBatis的配置文件,需要分别为两个数据源配置不同的mapper locations和datasource。
例如,在Spring Boot的application.yml中:
spring:
datasource:
pg:
jdbc-url: jdbc:postgresql://localhost:5432/pg_db
username: user
password: pass
driver-class-name: org.postgresql.Driver
ap:
jdbc-url: jdbc:postgresql://localhost:5432/ap_db
username: user
password: pass
driver-class-name: org.postgresql.Driver
然后,使用@Configuration类来配置两个数据源和对应的SqlSessionFactory。
例如:
@Configuration
@MapperScan(basePackages = "com.example.mapper.pg", sqlSessionFactoryRef = "pgSqlSessionFactory")
public class PgDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.pg")
public DataSource pgDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory pgSqlSessionFactory(@Qualifier("pgDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/pg/*.xml"));
return sessionFactory.getObject();
}
@Bean
public PlatformTransactionManager pgTransactionManager(@Qualifier("pgDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
同样的,再配置AP的数据源:
@Configuration
@MapperScan(basePackages = "com.example.mapper.ap", sqlSessionFactoryRef = "apSqlSessionFactory")
public class ApDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.ap")
public DataSource apDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory apSqlSessionFactory(@Qualifier("apDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/ap/*.xml"));
return sessionFactory.getObject();
}
@Bean
public PlatformTransactionManager apTransactionManager(@Qualifier("apDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
这样,两个数据源的Mapper接口分别位于不同的包(com.example.mapper.pg和com.example.mapper.ap),对应的XML文件也放在不同的目录(src/main/resources/mapper/pg和src/main/resources/mapper/ap)。
然后,在Service中注入两个Mapper,分别处理PG和AP的操作。
最后,测试代码,确保数据能够正确迁移,处理字段差异和冲突。
可能还需要处理数据库连接池的配置,比如使用HikariCP,设置最大连接数等,以防止资源不足。
总结:整个方案包括双数据源配置、实体类转换、分页查询、批量插入、动态SQL处理update_time字段,以及主键冲突的处理(通过清空表或ON CONFLICT语句)。</think>### 数据迁移实现方案(PG → AP)
#### 一、结构差异分析
1. **字段差异**
- PG表多出`id`和`recent_usage`字段
- AP表主键为`process_name`,PG表主键为`id`
2. **默认值差异**
- AP的`update_time`默认值为`now()`,PG允许为`NULL`
#### 二、技术实现步骤
##### 1. 双数据源配置
```java
// application.yml
spring:
datasource:
pg:
url: jdbc:postgresql://localhost:5432/pg_db
username: user
password: pass
ap:
url: jdbc:postgresql://localhost:5432/ap_db
username: user
password: pass
```
##### 2. 实体类定义
```java
// PG实体(含全部字段)
@Data
public class PgSoftwareDict {
private Integer id;
private String softwareName;
private String processName;
private Integer softwareType;
private Integer osType;
private Integer active;
private Integer recentUsage;
private Date createTime;
private Date updateTime;
}
// AP实体(排除id和recent_usage)
@Data
public class ApSoftwareDict {
private String softwareName;
private String processName;
private Integer softwareType;
private Integer osType;
private Integer active;
private Date createTime;
private Date updateTime;
}
```
##### 3. MyBatis Mapper配置
**PG查询Mapper**
```xml
<!-- PgSoftwareDictMapper.xml -->
<mapper namespace="com.example.mapper.PgSoftwareDictMapper">
<select id="selectPage" resultType="PgSoftwareDict">
SELECT software_name, process_name, software_type,
os_type, active, create_time, update_time
FROM vd.software_dict
ORDER BY process_name
LIMIT #{limit} OFFSET #{offset}
</select>
</mapper>
```
**AP插入Mapper**
```xml
<!-- ApSoftwareDictMapper.xml -->
<mapper namespace="com.example.mapper.ApSoftwareDictMapper">
<insert id="batchInsert" parameterType="list">
INSERT INTO vd.software_dict (
software_name, process_name, software_type,
os_type, active, create_time
<if test="item.updateTime != null">
, update_time
</if>
)
VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.softwareName}, #{item.processName}, #{item.softwareType},
#{item.osType}, #{item.active}, #{item.createTime}
<if test="item.updateTime != null">
, #{item.updateTime}
</if>
)
</foreach>
ON CONFLICT (process_name) DO UPDATE SET
software_name = EXCLUDED.software_name,
software_type = EXCLUDED.software_type,
os_type = EXCLUDED.os_type,
active = EXCLUDED.active,
update_time = EXCLUDED.update_time
</insert>
</mapper>
```
##### 4. 数据迁移服务
```java
@Service
@RequiredArgsConstructor
public class DataMigrationService {
private final PgSoftwareDictMapper pgMapper;
private final ApSoftwareDictMapper apMapper;
public void migrateData() {
int pageSize = 1000;
int offset = 0;
while (true) {
List<PgSoftwareDict> pgList = pgMapper.selectPage(offset, pageSize);
if (pgList.isEmpty()) break;
List<ApSoftwareDict> apList = pgList.stream()
.map(this::convertToAp)
.collect(Collectors.toList());
apMapper.batchInsert(apList);
offset += pageSize;
}
}
private ApSoftwareDict convertToAp(PgSoftwareDict pg) {
ApSoftwareDict ap = new ApSoftwareDict();
ap.setSoftwareName(pg.getSoftwareName());
ap.setProcessName(pg.getProcessName());
ap.setSoftwareType(pg.getSoftwareType());
ap.setOsType(pg.getOsType());
ap.setActive(pg.getActive());
ap.setCreateTime(pg.getCreateTime());
ap.setUpdateTime(pg.getUpdateTime());
return ap;
}
}
```
#### 三、关键处理逻辑
1. **字段过滤**
- 通过SQL查询排除`id`和`recent_usage`
2. **动态插入**
- 使用`<if>`标签动态处理`update_time`字段
3. **冲突处理**
- 使用`ON CONFLICT`实现UPSERT逻辑
4. **分页查询**
- 防止内存溢出,每次处理1000条数据
#### 四、部署建议
1. 在低峰时段执行迁移任务
2. 添加日志监控迁移进度
3. 增加重试机制处理网络波动
4. 验证数据完整性(记录数比对、关键字段校验)
> 提示:根据实际数据量调整分页大小,建议在测试环境验证后上线。
阅读全文
相关推荐


















