Spring Boot中如何用JPA将两张表归档到第三张表以避免重复代码?
Spring Boot 多表归档到统一表的无重复代码实现方案
针对多表(X、Y)归档到同一表A的场景,完全可以通过抽象公共逻辑、复用核心代码来避免重复,以下是几种实用方案:
1. 模板方法模式抽离公共归档逻辑
把归档流程的通用步骤(如数据查询、转换、批量插入)抽成抽象父类,让具体表的归档服务只实现差异化逻辑(比如从X/Y表取数据、字段映射)。
示例代码:
// 抽象归档服务 public abstract class AbstractArchiveService<T, A> { @Autowired private ARepository aRepository; // 公共归档入口 @Transactional public void archive() { // 1. 查询源表数据 List<T> sourceData = getSourceData(); // 2. 转换为目标表A的实体 List<A> targetData = convertToTarget(sourceData); // 3. 批量插入归档表 aRepository.saveAll(targetData); // 可选:归档后更新源表归档标记 updateSourceArchiveFlag(sourceData); } // 子类实现:获取对应源表的数据 protected abstract List<T> getSourceData(); // 子类实现:源实体转目标实体 protected abstract List<A> convertToTarget(List<T> sourceData); // 可选:子类实现更新源数据归档标记逻辑 protected void updateSourceArchiveFlag(List<T> sourceData) {} } // X表的具体归档服务 @Service public class XArchiveService extends AbstractArchiveService<XEntity, AEntity> { @Autowired private XRepository xRepository; @Override protected List<XEntity> getSourceData() { // 查询X表待归档的数据 return xRepository.findByArchiveFlag(false); } @Override protected List<AEntity> convertToTarget(List<XEntity> sourceData) { // X实体转A实体的映射逻辑 return sourceData.stream().map(x -> { AEntity a = new AEntity(); a.setField1(x.getXField1()); a.setField2(x.getXField2()); a.setSourceTable("X"); // 标记数据来源表 return a; }).collect(Collectors.toList()); } @Override protected void updateSourceArchiveFlag(List<XEntity> sourceData) { sourceData.forEach(x -> x.setArchiveFlag(true)); xRepository.saveAll(sourceData); } } // Y表的归档服务仅需替换Repository和字段映射逻辑,无需重复编写归档流程代码
2. 通用Repository + 动态数据转换
如果使用Spring Data JPA,可借助泛型和Bean映射工具(如MapStruct、ModelMapper)进一步简化字段转换,减少重复的映射代码。
示例:
// 通用实体转换工具类 @Component public class EntityConverter { @Autowired private MapStructMapper mapper; // 通用列表转换方法 public <S, T> List<T> convertList(List<S> sourceList, Class<T> targetClass) { return sourceList.stream() .map(source -> mapper.map(source, targetClass)) .collect(Collectors.toList()); } } // 通用归档服务 @Service public class GenericArchiveService { @Autowired private ARepository aRepository; @Autowired private EntityConverter converter; @Transactional public <S> void archive(JpaRepository<S, ?> sourceRepo, Specification<S> querySpec, Class<S> sourceClazz, String sourceTable) { List<S> sourceData = sourceRepo.findAll(querySpec); List<AEntity> targetData = converter.convertList(sourceData, AEntity.class); // 统一设置数据来源标记 targetData.forEach(a -> a.setSourceTable(sourceTable)); aRepository.saveAll(targetData); } } // 调用示例 @Service public class ArchiveManager { @Autowired private GenericArchiveService genericArchiveService; @Autowired private XRepository xRepository; @Autowired private YRepository yRepository; public void archiveXData() { // 构建X表的查询条件 Specification<XEntity> spec = (root, query, cb) -> cb.equal(root.get("archiveFlag"), false); genericArchiveService.archive(xRepository, spec, XEntity.class, "X"); } public void archiveYData() { Specification<YEntity> spec = (root, query, cb) -> cb.equal(root.get("archiveFlag"), false); genericArchiveService.archive(yRepository, spec, YEntity.class, "Y"); } }
3. MyBatis 动态表名 + 通用映射
如果使用MyBatis,可以通过动态SQL指定表名,配合ResultMap实现通用的数据查询和转换,避免重复编写Mapper接口和XML。
示例XML:
<!-- 通用归档查询:动态指定表名 --> <select id="selectSourceData" resultMap="aEntityResultMap"> SELECT x_field1 AS field1, x_field2 AS field2, #{sourceTable} AS source_table FROM ${tableName} WHERE archive_flag = 0 </select> <!-- 通用批量插入归档表 --> <insert id="batchInsertA"> INSERT INTO table_a (field1, field2, source_table) VALUES <foreach collection="list" item="item" separator=","> (#{item.field1}, #{item.field2}, #{item.sourceTable}) </foreach> </insert> <!-- 通用ResultMap:映射到A表实体 --> <resultMap id="aEntityResultMap" type="com.example.entity.AEntity"> <result property="field1" column="field1"/> <result property="field2" column="field2"/> <result property="sourceTable" column="source_table"/> </resultMap>
Mapper接口:
public interface ArchiveMapper { List<AEntity> selectSourceData(@Param("tableName") String tableName, @Param("sourceTable") String sourceTable); void batchInsertA(@Param("list") List<AEntity> dataList); }
服务层调用:
@Service public class ArchiveService { @Autowired private ArchiveMapper archiveMapper; @Transactional public void archive(String tableName, String sourceTable) { List<AEntity> data = archiveMapper.selectSourceData(tableName, sourceTable); archiveMapper.batchInsertA(data); // 可选:更新源表归档标记 archiveMapper.updateArchiveFlag(tableName); } }
关键注意事项
- 事务管理:所有归档操作必须添加
@Transactional,保证数据一致性,避免归档中途失败导致数据不一致。 - 批量性能优化:JPA需配置
spring.jpa.properties.hibernate.jdbc.batch_size控制批量提交大小;MyBatis需注意批量插入的SQL长度限制。 - 字段映射校验:使用映射工具时,要确保源表与目标表的字段映射正确,可通过单元测试校验转换逻辑。
内容的提问来源于stack exchange,提问作者KijeviGombooc
相关产品推荐
相关产品推荐

