分享十条Java后端开发实战经验,干货满满!
前沿
突然回首,博主算上大学到工作已经从事后端已经有好几年时间了,借助本篇文章,旨在对自己在公司、个人项目的实战经验总结,包括JAVA常遇到的业务场景技术栈、第三方库以及可复用的代码编写,希望能给大家带来帮助。
目前,我总结了10条常见的业务开发经验,毫无保留的分享给大家..., 主要涵盖内容如下:悟耕开源Easypoi Excel导入导出最佳实践Alibaba Excel导出时自定义格式转换优雅实现不建议直接使用@Async实现异步,需自定义线程池解决Java行业常见业务开发数值计算丢失精度问题Hutool TreeUtil快速构造返回树形结构事务@Transactional的失效场景Spring Event实现异步业务开发中通用的策略模式模板使用ip2region获取用户地址位置信息利用好Java现有优秀的开发库一、悟耕开源easypoi - Excel导入导出最佳实践
Excel导入导出几乎在很多中后台项目都会用到,特别是一些CRM、OA、商城、企业应用等系统都十分常见,在开发的过程中我也遇到过很多Excel大数据导入导出的功能,一直以来,使用easypoi做了不少导入导出的需求,导入导出数据量从10万级到现在百万级(Excel峰值103万数据量),整理了一下easypoi的导入导出的基础和高级用法。大数据导出数据转换以及数据加密Excel导入数据校验,并提供错误日志下载1.1 注解说明
常见的5个注解类分别是:@Excel :作用到filed上面,是对Excel列的一个描述;@ExcelCollection:表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示;@ExcelEntity:表示一个继续深入导出的实体,但他没有太多的实际意义,只是告诉系统这个对象里面同样有导出的字段;@ExcelIgnore:和名字一样表示这个字段被忽略跳过这个导导出;@ExcelTarget:这个是作用于最外层的对象,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理。1.2 定义easypoi实体类import cn.afterturn.easypoi.excel.annotation.Excel; import cn.afterturn.easypoi.handler.inter.IExcelDataModel; import cn.afterturn.easypoi.handler.inter.IExcelModel; import lombok.Data; import javax.validation.constraints.NotBlank; import java.io.Serializable; @Data public class SdSchoolSysUserVerify implements IExcelModel, IExcelDataModel, Serializable { private static final long serialVersionUID = 1L; @Excel(name = "行号") private Integer rowNum; @Excel(name = "错误信息") private String errorMsg; /** * 真实姓名 */ @Excel(name = "姓名(必填)", width = 25) @NotBlank(message = "姓名不能为空") private String realname; /** * 部门编码,需要和用户导入模板名称对应 */ @Excel(name = "部门编码(必填)", width = 30) @NotBlank(message = "部门编码不能为空") private String deptOrgCode; /** * 角色编码 */ @Excel(name = "角色编码(必填)", width = 15) @NotBlank(message = "角色编码不能为空") private String roleCode; /** * 手机号码 */ @Excel(name = "手机号码(选填)", width = 15) private String phone; /** * 电子邮件 */ @Excel(name = "电子邮件(选填)", width = 15) private String email; /** * 性别(1:男 2:女) */ @Excel(name = "性别(选填)", width = 15) private String sexName; /** * 工号(选填) */ @Excel(name = "工号(选填)", width = 15) private String workNo; /** * 商户ID **/ private Integer tenantId; } 复制代码1.3 基础的导入导出逻辑(数据校验)
easyPoi导入校验使用起来也很简单,以导入系统优化为例:
第一步,定义一个检验类SdSchoolSysUserVerify,通过实现IExcelModel、IExcelDataModel,当我们需要输出导入校验错误信息的时候,它们两个就显的很重要了,IExcelModel负责设置错误信息,IExcelDataModel负责设置行号。package cn.afterturn.easypoi.handler.inter; /** * Excel 本身数据文件 */ public interface IExcelDataModel { /** * 获取行号 */ public Integer getRowNum(); /** * 设置行号 */ public void setRowNum(Integer rowNum); } 复制代码
第二步,定义完实体之后,那么如何实现我们的校验逻辑呢,接着自定义一个系统用户导入校验处理器SdSchoolSysUserVerifyHandler,通过实现IExcelVerifyHandler,处理器里编写我们的校验逻辑:/** * 系统用户批量导入校验处理器 * * @author: jacklin * @since: 2021/3/31 11:47 **/ @Component public class SdSchoolSysUserVerifyHandler implements IExcelVerifyHandler { private static final String PREFIX = "【"; private static final String SUFFIX = "】"; @Autowired private ISysBaseAPI sysBaseAPI; @Override public ExcelVerifyHandlerResult verifyHandler(SdSchoolSysUserVerify userVerify) { LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); userVerify.setTenantId(Integer.valueOf(loginUser.getRelTenantIds())); StringJoiner joiner = new StringJoiner(", ", PREFIX, SUFFIX); if (StringUtils.isBlank(userVerify.getRealname())) { joiner.add("用户姓名不能为空"); } //根据用户姓名和商户ID查询用户记录,大于0则提示该姓名用户已存在 int realNameCount = sysBaseAPI.countByRealName(userVerify.getRealname(), userVerify.getTenantId()); if (realNameCount > 0) { joiner.add("该姓名用户已存在,如需添加该用户请在页面添加"); } if (StringUtils.isBlank(userVerify.getDeptOrgCode())) { joiner.add("部门编码不能为空"); } else { //查询系统是否存在该部门编码 int deptOrgCodeCount = sysBaseAPI.queryDepartCountByDepartSysCodeTenantId(userVerify.getDeptOrgCode(), userVerify.getTenantId()); if (deptOrgCodeCount == 0) { joiner.add("部门编码不存在"); } } if (oConvertUtils.isEmpty(userVerify.getRoleCode())) { joiner.add("用户角色编码不能为空"); } else { //查询系统是否存在该角色 int count = sysBaseAPI.queryRoleCountByRoleCodeTenantId(userVerify.getRoleCode(), userVerify.getTenantId()); if (count == 0) { joiner.add("该用户角色编码不存在"); } else { //查询配置是否用户支持导入该角色 int supportUserImportCount = sysBaseAPI.queryIsSupportUserImportByRoleCode(userVerify.getRoleCode(), userVerify.getTenantId()); if (supportUserImportCount == 0) { joiner.add("该用户角色编码不支持导入"); } } } if (oConvertUtils.isNotEmpty(userVerify.getPhone())) { boolean isPhone = Validator.isMobile(userVerify.getPhone()); if (!isPhone) { joiner.add("手机号填写格式不正确"); } } if (oConvertUtils.isNotEmpty(userVerify.getEmail())) { boolean isEmail = Validator.isEmail(userVerify.getEmail()); if (!isEmail) { joiner.add("邮箱填写格式不正确"); } } if (!"【】".equals(joiner.toString())) { return new ExcelVerifyHandlerResult(false, joiner.toString()); } return new ExcelVerifyHandlerResult(true); } } 复制代码
第三步,在完成第一、二步之后,我们只需要在导入的时候通过 params.setVerifyHandler(userVerifyHandler)、params.setNeedVerfiy(true)即可以实现导入校验了。1.4 不同类型数据的导入和导出(Map/Object)
在某些复杂的场景,我们导入的时候不想直接构造一个bean然后标记注解,但是中间需要处理一些字段逻辑没办法直接导入到数据库,这是用可以用map的形式导入,下面我以一个客户导入的需求演示一下如何通过map的方式导入数据:核心方法://Map数据格式导入 ExcelImportResult