项目需要把Excel导入,并且支持更新成果简介字段的值。这种Excel模版一共有9个,对应9大奖项,其中双创奖的导入模版如下:
主表为t_awards,主键用的是UUID,主要存储合并单元格部分的数据(一级分类、二级分类、获奖年份、成果名称等),通过award_type区分不同奖项;详细表为t_awards_detail,主要存储非合并单元格数据(获奖单位、获奖个人、员工编号等),award_id对应t_awards表的主键。
t_awards表结构如下:
t_awards_detail表结构如下:
截图中橙色为主键、绿色为普通索引、红色为联合唯一索引。
因为Excel中的数据没有唯一列,后端是通过唯一索引去做区分的。大致的实现逻辑如下:
官网原话:
If you specify an ON DUPLICATE KEY UPDATE clause and a row to be
inserted would cause a duplicate value in a UNIQUE index or PRIMARY
KEY, an UPDATE of the old row occurs. For example, if column a is
declared as UNIQUE and contains the value 1, the following two
statements have similar effect:
翻译为中文:
如果指定ON DUPLICATE KEY UPDATE子句,并且要插入的行将导致唯一索引或主键中出现重复值,则会更新旧行。例如,如果列a声明为唯一且包含值1,则以下两条语句具有类似的效果:
INSERT INTO t1 (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
UPDATE t1 SET c=c+1 WHERE a=1;
If column b is also unique, the INSERT is equivalent to this UPDATE
statement instead:
翻译为中文:
如果列b也是唯一的,则INSERT相当于这个UPDATE语句:
UPDATE t1 SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;
一句话总结就是"有就更新,没有就插入"。
官方文档还提到从MySQL 8.0.20开始,不推荐使用VALUES()来引入新行或者列,后续可能还会删除。
官方文档地址
Service层的主要代码如下所示:
@Override
public int doubleInnovation(List<ImportInnovationField> list) {
String userName = sessionUserApi.getSessionUser().getUsername();
// 1、对list根据多字段进行分组,得到map
Map<String,List<ImportInnovationField>> map = list.stream().collect(
Collectors.groupingBy(
field -> field.getTopClass()+CommonConstant.SEPARATOR_B+field.getSecondClass()+CommonConstant.SEPARATOR_B+field.getAwardYear()+CommonConstant.SEPARATOR_B+field.getAwardProduct()+CommonConstant.SEPARATOR_B+field.getBriefIntroduction()
));
// 2、循环map,把map键插入主表,返回主表主键id;把id封装到map值里面,批量插入从表
Gson gson = new Gson();
List<Awards> awardsList = new ArrayList<>();
List<AwardsDetail> detailList = new ArrayList<>();
for(Map.Entry<String,List<ImportInnovationField>> entrySet: map.entrySet()){
// 2.1 封装Awards并加入awardsList
String[] strs = entrySet.getKey().split(CommonConstant.SEPARATOR_B);
Awards awards = new Awards();
awards.setId(UUID.randomUUID().toString());
awards.setAwardType(AwardsEnum.AwardTypeEnum.INNOVATION_AND_ENTREPRENEURSHIP.getCode());
awards.setTopClass(strs[0]);
awards.setSecondClass(strs[1].equals(CommonConstant.NULL)?null:strs[1]);
awards.setAwardYear(strs[2]);
awards.setAwardProduct(strs[3]);
awards.setBriefIntroduction(strs[4].equals(CommonConstant.NULL)?null:strs[4]);
BigDecimal awardMoney = new BigDecimal(0);
for(ImportInnovationField field: entrySet.getValue()){
awardMoney = awardMoney.add(field.getMoney());
}
awards.setAwardMoney(awardMoney);
awards.setCreateUser(userName);
awards.setLastUpdateUser(userName);
awardsList.add(awards);
// 2.2 封装detailList
for(ImportInnovationField field: entrySet.getValue()){
AwardsDetail awardsDetail = gson.fromJson(gson.toJson(field),AwardsDetail.class);
awardsDetail.setAwardId(awards.getId());
awardsDetail.setCompanyType(setCompanyType(awardsDetail.getCompany()));
awardsDetail.setCreateUser(userName);
awardsDetail.setLastUpdateUser(userName);
detailList.add(awardsDetail);
}
}
// 3、 批量插入成果
batchInsertAwards(awardsList,detailList);
// 4、记录日志
List<String> businessIdList = awardsList.stream().map(Awards::getId).collect(Collectors.toList());
logService.batchInsertLog(businessIdList,"导入获奖成果",LogEnum.TypeEnum.AWARDS.getCode());
return 1;
}
// 批量插入获奖成果
private void batchInsertAwards(List<Awards> awardsList,List<AwardsDetail> detailList){
// 1、 把map键批量插入主表,若存在则更新成果简介等信息
try {
awardsImportMapper.batchInsertAwards(awardsList);
}catch (Exception e){
log.error("AwardsImportServiceImpl批量插入主表失败:"+e);
throw new CommonsException(MessageCode.Param_Error, CommonConstant.INSERT_TO_MAIN_TABLE_FAIL,e);
}
// 2、 把map的值批量插入从表
try {
awardsImportMapper.batchInsertAwardsDetail(detailList);
}catch (Exception e){
log.error("AwardsImportServiceImpl批量插入详细表失败:"+e);
throw new CommonsException(MessageCode.Param_Error, CommonConstant.INSERT_TO_FROM_TABLE_FAIL,e);
}
// 3、删除从表数据
// 3.1 从主表中查询出最近5分钟所有id
List<String> idList = awardsImportMapper.queryIdList();
// 3.2 从详细表中查询出最近5分钟所有awardId
List<String> awardIdList = awardsImportMapper.queryAwardIdList();
// 3.3 找出详细表比主表多的awardId(差集awardIdList-idList)用来删除从表数据
List<String> deleteIdList = awardIdList.stream().filter(item -> !idList.contains(item)).collect(toList());
// 3.4 删除详细表数据
if(CollUtil.isNotEmpty(deleteIdList)){
try {
awardsImportMapper.batchDeleteDetail(deleteIdList);
}catch (Exception e){
log.error("AwardsImportServiceImpl批量删除从表失败:"+e);
throw new CommonsException(MessageCode.Param_Error, CommonConstant.INSERT_TO_MAIN_TABLE_FAIL,e);
}
}
}
Dao层主要代码如下:
# id是Java代码生成的UUID,on duplicate key update可以更新主键或者唯一键的值但是不能和已有的数据重复
<insert id="batchInsertAwards">
insert into t_awards(
id, award_type, top_class, second_class, award_year,
award_product, brief_Introduction,award_money, innovation_points, application_effect,
display_diagram, project_data,patent_no, patent_type, apply_date,
organizer,create_user, create_time, last_update_user,update_time
)
values
<foreach collection="list" item="item" separator=",">
(
#{item.id},#{item.awardType},ifnull(#{item.topClass},''),ifnull(#{item.secondClass},''),#{item.awardYear},
ifnull(#{item.awardProduct},''),#{item.briefIntroduction},#{item.awardMoney},#{item.innovationPoints},#{item.applicationEffect},
#{item.displayDiagram},#{item.projectData},ifnull(#{item.patentNo},''),#{item.patentType},#{item.applyDate},
#{item.organizer},#{item.createUser},now(),#{item.lastUpdateUser},now()
)
foreach>
on DUPLICATE key update brief_Introduction = VALUES(brief_Introduction),organizer = VALUES(organizer),
patent_type = VALUES(patent_type),apply_date = VALUES(apply_date),update_time = now()
insert>
参考链接1
参考链接2
参考链接3