在grails中实现excel导入的有效方法



这段代码可能应该进行代码审查,但我不会在那里得到快速响应(那里只有两个很棒的问题)。我有以下代码用于将excel中的数据导入我的grails应用程序。问题是,我没有在excel文件中测试>1000行,所以当我的客户试图上传13k行时,我的应用程序冻结了。我已经检查了stacktracke.log(应用程序正在生产中),但没有异常。系统管理员认为jvm内存不足。我们增加了堆内存的大小但是,我想问一下是否有更好的方法来实现这一点我正在使用apachepoi,并在从excel中读取每一行时创建域对象。之后,我将对象列表传递给控制器,控制器验证并将它们保存在数据库中我应该告诉我的客户限制一次导入的项目数量吗是否有更好的方法来编写此代码

def importData(file, user){
def rows = []
def keywords = Keyword.list()
int inventoryCount = Inventory.findAllByUser(user).size()
def inventory = new Inventory(name:"Inventory ${inventoryCount +1}", user:user)
Workbook workbook = WorkbookFactory.create(file)
Sheet sheet = workbook.getSheetAt(0)
int rowStart = 1;
int rowEnd = sheet.getLastRowNum() + 1 ;
for (int rowNum = rowStart; rowNum < rowEnd; rowNum++) {
Row r = sheet.getRow(rowNum);
if(r != null && r?.getCell(0, Row.RETURN_BLANK_AS_NULL)!=null ){
def rowData =[:]
int lastColumn = 8;
for (int cn = 0; cn < lastColumn; cn++) {
Cell c = r.getCell(cn, Row.RETURN_BLANK_AS_NULL);
if (c == null) {
return new ExcelFormatException("Empty cell not allowed",rowNum+1, cn+1)
} else {
def field = properties[cn+1]
if(field.type==c.getCellType()){
if(c.getCellType()==text){
rowData<<[(field.name):c.getStringCellValue().toString()]
}else if(c.getCellType()==numeric){
if(field.name.equalsIgnoreCase("price") ){
rowData<<[(field.name):c.getNumericCellValue().toDouble()]
}else{
rowData<<[(field.name):c.getNumericCellValue().toInteger()]
}
}
}else{
return new ExcelFormatException("Invalid value found", rowNum+1, cn+1)
}
}
}
def item = new InventoryItem(rowData)
String keyword = retrieveKeyword(item.description, keywords)
String criticality = keyword?"Critical":"Not known"
int proposedMin = getProposedMin(item.usagePerYear)
int proposedMax = getProposedMax(proposedMin, item.price, item.usagePerYear, item?.currentMin)
String inventoryLevel = getInventoryLevel(item.usagePerYear, item.quantity, proposedMin, item.currentMin) 
item.proposedMin = proposedMin
item.proposedMax = proposedMax
item.inventoryLevel = inventoryLevel
item.keyword = keyword 
item.criticality = criticality
inventory.addToItems(item)
}
}
return inventory
}

上述代码中使用的功能:

def retrieveKeyword(desc, keywords){
def keyword
for (key in keywords){
if(desc.toLowerCase().contains(key.name.toLowerCase())){
keyword = key.name
break
}
}
return keyword
}
int getProposedMin(int usage){
(int) ((((usage/12)/30) *7) + 1)
}

int getProposedMax(int pmin, double price, int usage, int cmin){
int c = price == 0? 1: ((Math.sqrt((24 * (usage/12)*5)/(0.15*price))) + (pmin - 1))
if(cmin >= c){
return pmin
}
return c
}
String getInventoryLevel(int usage, int qty, int proposedMin, int currentMin){
if(qty != 0){
double c = usage/qty
if(usage==0)
return "Excess"
if(c<0.75){
return "Inactive"
}else if(proposedMin<currentMin){
return "Excess"
}else if(c>=0.75){
return "Active"
}
}else if(usage==0 && qty == 0){
return "Not used"
}else if(usage>3 && qty ==0){
return "Insufficient"
}else if(proposedMin > currentMin){
return "Insufficient"
}
}

控制器动作:

def importData(){
if(request.post){
def file = request.getFile("excelFile")
//validate file
def file_types = ["application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
if(!file_types.contains(file.getContentType())){
render view:"importData", model:[error:"Invalid File type"]
return
}
def inv = excelService.importData(file.getInputStream(),User.get(principal.id))
if(inv){
if(inv instanceof ExcelFormatException){
def err = (ExcelFormatException) inv
render view:"importData", model:[error:err.message +". Error occurred at: Row: "+err.row+" Col: "+err.col]
return
}else{
render view:"viewData", model:[inventory:inv]
return
}
}
}

}

Hibernate和GORM在处理批量导入时需要进行一些调优。两个建议:

  1. 遵循此处找到的技术:http://naleid.com/blog/2009/10/01/batch-import-performance-with-grails-and-mysql(编写时考虑到MySQL,但这些概念与任何RDBMS都相关)

  2. 不要使用集合来映射InventoryInventoryItem之间的关系。从Inventory中删除items集合,而将Inventory字段添加到InventoryItem类中。Burt Beckwith在这里详细介绍了这一点:http://burtbeckwith.com/blog/?p=1029

使用插件是一个更好的选择。我使用这个插件-http://grails.org/plugin/excel-import

最新更新