一个新项目,做一个数据统计的功能,源数据需要从另外一个机构获取,获取方式和数据入库方式各种扯淡,源数据提供方只愿意通过EXCEL提供数据,项目部署后又不给操作数据库,关键数据还得每个月更新,所以只能先简单的做一个导入功能将源数据入库了。
目前只是简单的实现了功能,有不正确的地方或者有更好的方法可以一起讨论。
代码如下,不足之处望大神多多指教
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table>
<form action="/xxx/xxx/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
</table>
</body>
</html>
原本使用的@Data注解,反射调用时,注解失效,后边再看一下,有大神知道的望不吝赐教
@Component
@Named("user")
public class User {
private String objId;
private String name;
private String sex;
private Date createDate;
public String getObjId() {
return objId;
}
public void setObjId(String objId) {
this.objId = objId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
定义的枚举类,配置导入表对应的service和实体类在上下文中bean的id,有更好的方式的可以提出来哦
public enum ImportEnum {
//自行扩展,Student。。。。
user("userService", "user", "USER");
//获取对应service的bean ID
private String serviceName;
//对应的实体类的bean ID
private String pojoName;
//描述-表名称
private String desc;
private ImportEnum(String serviceName, String pojoName, String desc) {
this.serviceName = serviceName;
this.pojoName = pojoName;
this.desc = desc;
}
public static String getServiceName(String desc) {
ImportEnum[] carTypeEnums = values();
for (ImportEnum carTypeEnum : carTypeEnums) {
if (carTypeEnum.desc().equals(desc)) {
return carTypeEnum.serviceName();
}
}
return null;
}
public static String getPojoName(String desc) {
ImportEnum[] carTypeEnums = values();
for (ImportEnum carTypeEnum : carTypeEnums) {
if (carTypeEnum.desc().equals(desc)) {
return carTypeEnum.pojoName();
}
}
return null;
}
private String serviceName() {
return this.serviceName;
}
private String pojoName() {
return this.pojoName;
}
private String desc() {
return this.desc;
}
}
把Map转为Java POJO对象,适用于对象包含List, Map, 数组, Date等
@Component
public class ReflectUtil {
private Class<?> classBind;
public ReflectUtil() {
}
public <T> void registerClass(Class<T> cl) {
classBind = cl;
}
public Object handler(Map<String,Object> map, Object t) {
ReflectUtil util = new ReflectUtil();
util.registerClass(t.getClass());
Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,Object> entry = iter.next();
String key = entry.getKey();
Object val = entry.getValue();
util.setValue(key, val, t);
}
return t;
}
/**
* 根据反射设置某字段的值
*
* @param name
* @param value
*/
public <T> void setValue(String name, Object value, Object ob) {
try {
if (null == value || null == name) {
return;
}
//PropertyDescriptor 属性描述器
//获取bean的某个属性的描述符
PropertyDescriptor pd = new PropertyDescriptor(name, this.classBind);
//获得用于写入属性值的方法
Method wM = pd.getWriteMethod();
//获得属性的Class对象
Class<?> parameterType = wM.getParameterTypes()[0];
if (parameterType == String.class) {
// 写入属性值
wM.invoke(ob, String.valueOf(value));
} else if (parameterType == int.class || parameterType == Integer.class) {
String str = String.valueOf(value);
if (StringUtils.isNotBlank(str)) {
wM.invoke(ob, new BigDecimal(String.valueOf(value)).intValue());
}
} else if (parameterType == double.class || parameterType == Double.class) {
String str = String.valueOf(value);
if (StringUtils.isNotBlank(str)) {
wM.invoke(ob, new BigDecimal(String.valueOf(value)).doubleValue());
}
} else if (parameterType == Date.class) {
String str = String.valueOf(value);
if (StringUtils.isNotBlank(str)) {
if (str.length() > 10) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
wM.invoke(ob, sdf.parse(str));
} else {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
wM.invoke(ob, sdf.parse(str));
}
}
} else if (parameterType.isArray()) {
Object[] list = null;
if (value instanceof List) {
list = ((List<?>) value).toArray();
} else {
list = (Object[]) value;
}
Object[] args = new Object[list.length];
for (int index = 0; index < list.length; index++) {
Object item = list[index];
if (item instanceof Number) {
args[index] = item;
} else if (item instanceof String) {
args[index] = item;
} else {
args[index] = item;
}
}
wM.invoke(ob, args);
} else {
Type[] parameters = wM.getGenericParameterTypes();
if (parameters.length > 0) {
if (parameters[0] instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) parameters[0];
Type[] actualTypeArguments = pt.getActualTypeArguments();
if (actualTypeArguments.length == 1) {
List<Object> args = new ArrayList<Object>();
Object[] list = null;
if (value instanceof List) {
list = ((List<?>) value).toArray();
} else {
list = (Object[]) value;
}
for (Object item : list) {
if (item instanceof Number) {
args.add(item);
} else if (item instanceof String) {
args.add(item);
} else {
args.add(item);
}
}
wM.invoke(ob, args);
} else if (actualTypeArguments.length == 2) {
Map<Object, Object> targetMp = (Map<Object, Object>) value;
Map<Object, Object> args = new HashMap<>();
for (Object key : targetMp.keySet()) {
Object val = targetMp.get(key);
if (val instanceof Number) {
args.put(key, val);
} else if (val instanceof String) {
args.put(key, val);
} else {
args.put(key, val);
}
}
wM.invoke(ob, args);
}
}
}
}
} catch (Exception e) {
System.out.println("处理字段失败:" + name);
e.printStackTrace();
}
}
}
@RestController
@RequestMapping(value = "/xxx/xxx")
public class ImportExcelController {
Logger logger = LoggerFactory.getLogger(ImportExcelController.class);
@Autowired
private ImportExcelService importExcelService;
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
return importExcelServiceTwo.ImportData(file);
}
}
@Service("userService")
public class UserService {
@Autowired
UserMapper userMapper;
//如果使用cl.newInstance()方式,mapper注入失败 解决方法 加入以下代码
//public static UserService dynamicProxy;
// @PostConstruct
//public void init () {
// dynamicProxy = this;
//}
/**
* 删除原有数据,导入新数据
* @param date 日期,全量删除不需要,增量需要根据日期做对应的删除
*/
public void delete(String date) {
try {
Map<String,Object> map = new HashMap<>();
map.put("xxx",date);
userMapper.deleteAll(map);
//cl.newInstance()方式导致mapper注入失败的使用下面这种方式
//dynamicProxy.userMapper.deleteAll(map);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 插入新数据
* @param date 需要导入的数据
*/
public void batchInserts(List<Object> date) {
try {
userMapper.batchInsertImpoer(date);
//cl.newInstance()方式导致mapper注入失败的使用下面这种方式
//userMapper.batchInsertImpoer(date);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service
public class ImportExcelServiceTwo {
@Value("${batch.processing.number}")
private int batchProcessingNumber;
@Autowired
private ApplicationContext context;
/**
* 开始导入数据
* @param file
* @return
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)
public String ImportData (MultipartFile file) throws IOException {
//获取文件名称,文件名称采用 表明#日期的格式,比如: USER#2020-03
//数据是每个月都要导入,所以加一个日期区分,这个根据自己需求修改
String fileName = file.getOriginalFilename();
//表名,用来获取应用个上下文
String tableName = fileName.substring(0,fileName.indexOf("#"));
//获取数据的时间
String date = fileName.substring(fileName.indexOf("#") + 1,fileName.indexOf("."));
InputStream inputStream = file.getInputStream();
Workbook book = null;
if (isExcel2003(fileName)) {
book = new HSSFWorkbook(inputStream);
}
if (isExcel2007(fileName)) {
book = new XSSFWorkbook(inputStream);
}
Sheet sheet = book.getSheetAt(0);
//总条数
int allRowNum = sheet.getLastRowNum();
Instant startNow = Instant.now();
System.out.println("开始执行" + tableName + "数据");
try {
// 也可以使用Class.forName的方式获取,这种方式需要类对应的全部路径,不灵活
// Class<?> cl = Class.forName(xxx.xxx.xxx.xxx.service);
//ImportEnum 枚举类,用来获取导入表 对应的service和实体类的bean的Id
Class<?> cl = context.getBean(ImportEnum.getServiceName(tableName)).getClass();
//也可以使用Object o = cl.newInstance();的方式但是这种方式会导致mapper注入失效,需要单独处理,处理方法会在service中说明
//使用context.getBean 直接从应用上下文中获取service
Object o = context.getBean(ImportEnum.getServiceName(tableName));
//获取删除数据方法,因为可能会重复导入某月数据,所以先删除操作,加了删除方法,根据自己实际需求来
Method deleteM = cl.getDeclaredMethod("delete", String.class);
//获取批量插入方法
Method batchInsertM = cl.getDeclaredMethod("batchInserts", List.class);
//先删除导入月份数据,防止数据重复
//可能会重复导入某个月的数据,所以要先做对应的删除,然后在插入新数据
//因为插入时是批量插入,所以把删除操作拿了出来,没有和新增放一起
deleteM.invoke(o,date);
//获取列头,用于后边将excel数据转换为实体数据
Row row = sheet.getRow(0);
String [] ColumnHeads = new String[300];
IntStream.range(1, row.getLastCellNum()).forEach(i -> {
Cell cell = row.getCell(i);
cell.setCellType(CellType.STRING);
//将下划线转换为驼峰,与pojo对应,EXCEL是数据库导出来的数据,列头都是数据库字段名称,要和实体类保持一致 create_date -> createDate
//自己可以根据实际情况是否需要
if (StringUtils.isNotBlank(cell.getStringCellValue())) {
ColumnHeads[i] = Utils.lineToHump(cell.getStringCellValue().toLowerCase());
}
});
//批量处理新增
//batchProcessingNumber代表每次批量插入条数,在配置文件中,自己视情况配置
//根据批量处理条数,获取批量插入次数
int pages = (allRowNum + batchProcessingNumber - 1) / batchProcessingNumber;
Class w = context.getBean(ImportEnum.getPojoName(tableName)).getClass();
Constructor c = w.getConstructor();
for (int i = 1; i <= pages; i++) {
List<Object> lists = new ArrayList<>();
int fromIdx = (i - 1) * batchProcessingNumber;
int num = i * batchProcessingNumber > allRowNum ? allRowNum : (i * batchProcessingNumber);
//没有使用Stream 循环方式,异常后无法终止整个循环,改为for
for (int j = fromIdx + 1; j <= num; j++) {
Map<String, Object> beans = new HashMap<>();
//获取第j行数据
Row rowBean = sheet.getRow(j);
//将j行每列数据处理到map中
for (int k = 1; k < rowBean.getLastCellNum(); k++) {
Cell cell = rowBean.getCell(k);
cell.setCellType(CellType.STRING);
beans.put(ColumnHeads[k],cell.getStringCellValue());
};
try {
Object water = c.newInstance();
//也可以直接使用 Object object = ReflectUtil.handler(beans, water)实现
//原本想着后边系统使用AOP来做日志统一处理的,使用上面那种方式,通知会失效,所以采用了直接从应用上下文获取的方式
ReflectUtil reflectUtil = context.getBean(ReflectUtil.class);
Object object = reflectUtil.handler(beans, water);
lists.add(object);
} catch (Exception e) {
e.printStackTrace();
return tableName + "表导入数据异常,转换实体类关系失败:" + e.getMessage();
}
};
System.out.println("开始批量插入第" + (fromIdx + 1) + "到" + (num + 1) + "条数据");
batchInsertM.invoke(o,lists);
}
Instant endTime = Instant.now();
System.out.println(tableName + "表数据插入结束,耗时" + Duration.between(startNow,endTime).toMillis()+"毫秒");
} catch (Exception e) {
e.printStackTrace();
return tableName + "表导入数据异常" + e.getMessage();
}
return "success";
}
}
@Mapper
public interface UserMapper {
int deleteAll(Map<String, Object> map);
int batchInsertImpoer(List<Object> records);
}
使用的是Oracle数据库,批量插入写法根据实际使用数据库进行修改
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sgcc.pms.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.sgcc.pms.pojo.User">
<id column="OBJ_ID" property="objId" jdbcType="VARCHAR"/>
<result column="NAME" property="name" jdbcType="VARCHAR"/>
<result column="SEX" property="sex" jdbcType="VARCHAR"/>
<result column="CREATE_DATE" property="createDate" jdbcType="TIMESTAMP"/>
</resultMap>
<delete id="deleteAll" parameterType="map">
delete from USERS
</delete>
<insert id="batchInsertImpoer" parameterType="java.util.List">
INSERT ALL
<foreach collection="list" item="setting" index="index">
into USERS (OBJ_ID, NAME, SEX, CREATE_DATE
)
values
(#{setting.objId,jdbcType=VARCHAR}, #{setting.name,jdbcType=VARCHAR},
#{setting.sex,jdbcType=VARCHAR}, #{setting.createDate,jdbcType=TIMESTAMP})
</foreach>
SELECT 1 FROM DUAL
</insert>
</mapper>
网友评论