excel导入导出优化
对于上一篇excel中出现的问题,在excel导入导出中都做了优化.还是eclipse+jdk1.8,但是这个项目是一个web项目,需要配合Tomcat服务器,并且使用了SSH框架,
I/O操作过多
首先,对于I/O操作过多,那么就不像之前一样,一条一条的添加或者更新;而且凑齐一堆,也就是一个list集合,然后统一的批量保存.
使用SessionFactory获取当前的session,然后调用session的persist方法,保存实体.只是设置了一个批量的量值.每到30条数据,就将缓存同步到数据库中.
/**
* 批量保存
*/
@Override
public <T> boolean saveEntitys(List<T> entitys) {
boolean flag = false;
int batchCount = 30;
try {
for (int i = 1; i <= entitys.size(); i++) {
getSession().persist(entitys.get(i - 1));
if (i % batchCount == 0) {
getSession().flush();
getSession().close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
Excel中英文
然后是excel模板必须固定,不能更换列的顺序,且是英文字段的问题.excel模板必须固定是因为我们没有从excel中获取表头,并记录每个表头是在那一列,这样就可以根据表头中的值,知道列号,然后根据列号去相应的列中取值.
// 将列名和列号放入Map中,这样下面通过列名就能获取列号,
LinkedHashMap<String, Integer> colNameNoMap = new LinkedHashMap<String, Integer>();
for (int i = 0; i < excelFiledNameArray.length; i++) {
colNameNoMap.put(excelFiledNameArray[i],
headerRow[i].getColumn());
}
而英文问题,需要有一个中文对照表,这里就将中文和英文的对照表放到xml文件中.在WebContent文件夹下,放着studentImport.xml的配置文件.内容就是如下,这里面设置了需要导入的excel的实体,导入的excel中的Sheet名称,以及导入的普通字段是中英文对照哪些,主外键关联的字段是哪些(目前还没弄出来),以及唯一不可重复的字段是哪些.而这些配置了的字段,不论是普通字段,主外键关联字段还是唯一键字段都是excel中必须存在的字段的
<?xml version="1.0" encoding="UTF-8"?>
<!--导入"Student"(下载Excel模板 和 导入Student Excel) -->
<importExcel>
<Entity entityName="com.tgb.entity.Student" sheetName="考生">
<!--1:NormalFieldMaps——要导入的实体的 普通字段。。。Start。。。 -->
<NormalFieldMaps>
<NormalField name="学号" code="stuNo"></NormalField>
<NormalField name="姓名" code="stuName"></NormalField>
<NormalField name="性别" code="stuSex"></NormalField>
<NormalField name="学院" code="department"></NormalField>
<NormalField name="专业" code="major"></NormalField>
</NormalFieldMaps>
<!--1:NormalFieldMaps——要导入的实体的 普通字段。。。End。。。 -->
<!--2:UniqueFieldMaps——要导入的实体中,数据不可重复的字段。。。Start。。。 -->
<UniqueFieldMaps>
<UniqueField name="学号">
</UniqueField>
</UniqueFieldMaps>
<!--2:UniqueFieldMaps——要导入的"教师"实体中,数据不可重复的字段。。。End。。。 -->
</Entity>
</importExcel>
而我们需要做的就是用Jdom或者Dom4j工具,读取这个xml文件,获取根节点importExcel,以及他的子节点Entity,可以是多个.并且Entity中的属性,是什么也要获取到.用entityName可以反射出实体的实例,而子节点NormalFieldMaps和UniqueFieldMaps可以将里面的中英文字段对照表放到map中,以及放到list中.只需要解析NormalField节点的属性name和code,以及UniqueField的name属性.
// 记录普通节点
private LinkedHashMap<String, String> normalFieldMap;
// 记录唯一节点
private List<String> uniqueFieldList;
// 数组的形式记录唯一节点
private String[] uniqueFieldArray = null;
/******************** 解析{导入xml}到map中 start *******************************/
/**
* 将导入的excel解析
*
* @param inXMLFile
*/
private void importExcelXml2Map(InputStream inXMLFile) {
try {
// 1.检测
if (null == inXMLFile) {
throw new FileNotFoundException();
}
// 2.解析,使用dom4j
SAXReader reader = new SAXReader();
Document doc = reader.read(inXMLFile);
// 根元素,importExcel
Element root = doc.getRootElement();
// 3.获取Entity节点
Iterator iterator = root.elements("Entity").iterator();
while (iterator.hasNext()) {
Element entityElement = (Element) iterator.next();
// 3.1.获取属性,entityName,以及导入的实体
String entityName = entityElement.attributeValue("entityName");
entityClazz = Class.forName(entityName);
// 实例化,普通字段的map和唯一字段的list,用于放xml中的值
// 链表的Map,空间不连续,添加,修改,删除快,读不快
normalFieldMap = new LinkedHashMap<String, String>();
uniqueFieldList = new ArrayList<String>();
// 解析普通字段节点
parseEntityNormalFieldMaps(entityElement);
// 解析唯一字段节点
parseEntityUniqueFieldMaps(entityElement);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解析普通字段的Map集合节点
*
* @param entityElement
*/
private void parseEntityNormalFieldMaps(Element entityElement) {
if (null != entityElement) {
Iterator iterator = entityElement.elements("NormalFieldMaps")
.iterator();
while (iterator.hasNext()) {
Element normalFiledMapsElement = (Element) iterator.next();
parseNormalFieldMaps(normalFiledMapsElement);
}
}
}
/**
* 解析普通字段,在Map集合下的
*
* @param normalFiledMapsElement
*/
private void parseNormalFieldMaps(Element normalFiledMapsElement) {
if (null != normalFiledMapsElement) {
Iterator iterator = normalFiledMapsElement.elements("NormalField")
.iterator();
while (iterator.hasNext()) {
Element normalFileElement = (Element) iterator.next();
// 将节点上的值,赋值到Map中
setNormalFieldMap(normalFileElement);
}
}
}
/**
* 将普通字段的name和code,赋值到normalFieldMap上
*
* @param normalFileElement
*/
private void setNormalFieldMap(Element normalFileElement) {
if (null != normalFileElement) {
// 字段中文
String name = normalFileElement.attributeValue("name");
// 字段英文
String code = normalFileElement.attributeValue("code");
normalFieldMap.put(name, code);
}
}
/**
* 解析唯一字段的Map集合节点
*
* @param entityElement
*/
private void parseEntityUniqueFieldMaps(Element entityElement) {
if (null != entityElement) {
Iterator iterator = (Iterator) entityElement.elements(
"UniqueFieldMaps").iterator();
while (iterator.hasNext()) {
Element uniqueFieldMapsElement = (Element) iterator.next();
parseUniqueFieldMaps(uniqueFieldMapsElement);
}
}
}
/**
* 解析唯一字段,在map集合下的
*
* @param uniqueFieldMapsElement
*/
private void parseUniqueFieldMaps(Element uniqueFieldMapsElement) {
if (null != uniqueFieldMapsElement) {
Iterator iterator = (Iterator) uniqueFieldMapsElement.elements(
"UniqueField").iterator();
while (iterator.hasNext()) {
Element uniqueFieldElement = (Element) iterator.next();
setUniqueFieldList(uniqueFieldElement);
}
setUniqueFieldArray(uniqueFieldList);
}
}
/**
* 将唯一字段的值设置到list中
*
* @param uniqueFieldElement
*/
private void setUniqueFieldList(Element uniqueFieldElement) {
if (null != uniqueFieldElement) {
// 唯一字段的中文名,对应普通字段的中文名
String name = uniqueFieldElement.attributeValue("name");
uniqueFieldList.add(name);
}
}
/**
* 将list中的唯一值,放到Array数组中
*
* @param uniqueFieldList
*/
private void setUniqueFieldArray(List<String> uniqueFieldList) {
if (null != uniqueFieldList) {
int size = uniqueFieldList.size();
uniqueFieldArray = new String[size];
for (int i = 0; i < size; i++) {
uniqueFieldArray[i] = uniqueFieldList.get(i);
}
}
}
/******************** 解析{导入xml}到map中 end *********************************/
这样就可以提供这样的excel模板了.表头变为中文,不用写id,hibernate可以用主键生成策略生成id.并且各列的顺序也可以随意,只是必须有这些字段.
Excel路径
而excel的路径写死的问题,只需要一个界面,上传工具.下载的时候通过response放到界面上.
文件上传
jsp页面中的内容,提供上传功能.
<body>
<form id="importExcelForm" enctype="multipart/form-data" method="post" >
<input id="fileBox" name="fileBox" class="easyui-filebox" style="width:400px" data-options="prompt:'请选择文件'"/>
<a id="importButton" href="#" class="easyui-linkbutton" onclick="importExcel()">导入Excel</a>
</form>
<script type="text/javascript">
//点击弹出选择文件框的按钮
$('#fileBox').filebox({
buttonText :'选择文件',
buttonAlign :'right'
});
function importExcel() {
//获取文件
var fileName = $('#fileBox').filebox('getValue');
//基本的校验
if(fileName == "") {
alert("请选择上传的文件!");
}else {
var fileNameSuf = /\.[^\.]+$/.exec(fileName);
if(fileNameSuf != ".xls") {
alert("请选择xls格式文件!");
$("#fileBox").filebox('setValue','');
}else {
$('#importExcelForm').form('submit',{
url:"${pageContext.request.contextPath}/importExcel",
onSubmit:function() {
return $(this).form('validate');
},
success:function(result) {
alert(result)
var result = eval(result);
if(result == false) {
$.messager.alert("提示","导入失败,请检查excel数据!","info");
}else if(result == true) {
$.messager.alert("提示","导入成功!","info");
}else {
$.messager.alert("提示","浏览器不兼容,请更换浏览器","info");
}
}
});
}
}
}
</script>
而Contorller接收到的上传请求,会将excel和导入xml都变为输入流,然后调用工具类的导入方法,将excel导入.导入方法,主要也是解析导入xml,获取里面的配置,以及获取excel中的数据,将数据变为实体集合,最后将实体集合使用批量保存,保存到数据库中.当然,将excel的数据变成list的时候,需要对数据进行检测,若配置了唯一字段,则该字段中的值,必须是唯一的.
/**
* 导入Excel
*/
@RequestMapping(value="importExcel")
private void importExcel(
@RequestParam("fileBox") CommonsMultipartFile excelFile,
HttpServletRequest request,HttpServletResponse response) {
//接收excel文件变为输入流
InputStream inExcelFile = null;
try {
inExcelFile = excelFile.getInputStream();
} catch (IOException e) {
System.err.println("excel读取出错。");
e.printStackTrace();
}
//获取xml配置文件,也变为输入流
String xmlFilePath = request.getSession().getServletContext().getRealPath("/")+"studentImport.xml";
File importXMLFile = new File(xmlFilePath);
InputStream inXMLFile = null;
try {
inXMLFile = new FileInputStream(importXMLFile);
} catch (FileNotFoundException e) {
System.err.println("未找到xml文件。");
e.printStackTrace();
}
try {
//导入excel到数据库中,使用工具类parseImportExcelXml
Boolean flag = parseImportExcelXml.importExcel(inExcelFile,inXMLFile,response);
jacksonJsonUntil.beanToJson(response, flag);
}catch(Exception e) {
System.err.println("excel导入失败。");
e.printStackTrace();
}
}
文件下载
下载到界面上,这里创建Workbook不是写一个文件路径,而是写response的输出流.然后添加表头,添加内容,设置excel名,以及response的一些参数,内容类型和头文件.
errorLines = errorMap.size();
// 创建错误列表工作簿和sheet
WritableWorkbook ewwb = Workbook.createWorkbook(response
.getOutputStream());
WritableSheet eSheet = ewwb.createSheet("错误列表", 0);
// 添加表头
for (int n = 0; n < excelFiledNameArray.length; n++) {
Label label = new Label(n, 0, excelFiledNameArray[n]);
eSheet.addCell(label);
}
// 并添加两列,错误原因和所在行号
eSheet.addCell(new Label(excelFiledNameArray.length, 0, "错误原因"));
eSheet.addCell(new Label(excelFiledNameArray.length + 1, 0,
"所在行号"));
// 添加内容
... addErrorRow(sheet, eSheet, errorMap);
// 设置文件名,当前时间
String fileName = new SimpleDateFormat("yyyyMMddhhmmss")
.format(new Date()).toString() + "ErrorData";
response.reset();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition",
"attachment;filename=" + fileName + ".xls");
//设置错误excel的单元格大小
setColumnAutoSize(eSheet, 10);
//写入数据,并关闭工作簿
ewwb.write();
ewwb.close();
附注
运行的效果
点击导入Excel,导入成功,数据库中的数据如下.
再次导入Excel,由于数据重复,所以,生成错误excel,并且数据不写入数据库中.
项目下载:Excel导入导出优化