1. 程式人生 > >java excel匯入並多執行緒批量插入資料庫

java excel匯入並多執行緒批量插入資料庫

最近寫了個excel匯入並多執行緒持久化到資料庫的功能,搗鼓了一天才弄好,先記錄下來防止自己忘了。

(1)先controller類中方法。

@AccessLog
    @ApiOperation(value = "匯入excel", httpMethod = "POST", notes = "匯入excel")
    @RequestMapping(value = "/importExcel",method = RequestMethod.POST)
    @ApiImplicitParams({
            @ApiImplicitParam(name="postionId",value="崗位ID",dataType="long", paramType = "query"),
            @ApiImplicitParam(name="typeId",value="型別ID(1:崗位 2:人員)",dataType="int", paramType = "query"),
            @ApiImplicitParam(name="agencyId",value="部門ID",dataType="long", paramType = "query")
    })
    public ResponseResult importExcel(@RequestParam(value="file") MultipartFile file,
                                        Long postionId, Integer typeId, Long agencyId) {
        SelAgencyAndPostionVO selAgencyAndPostionVO = new SelAgencyAndPostionVO(agencyId,postionId,typeId);
        if (null == selAgencyAndPostionVO) {
            return new ResponseResult(ExceptionCode.PARAM_IS_NULL);
        }
        //型別標識(1:崗位 2:人員)
        typeId = selAgencyAndPostionVO.getTypeId();
        if (null == typeId) {
            log.info("1", "typeId is null");
            return new ResponseResult(ExceptionCode.PARAM_IS_NULL);
        }
        //獲取上傳的檔名稱;
        String name = file.getOriginalFilename();
        //判斷是否為excel型別檔案
        if(!name.endsWith(".xls") && !name.endsWith(".xlsx")){
            log.info("匯入的檔案不是excel型別");
            return  new ResponseResult<>("匯入的檔案不是excel型別");
        }

        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.
                    getRequestAttributes()).getRequest();

            //上傳至絕對路徑
            String path = request.getSession().getServletContext().getRealPath(File.separator);
            String uploadDir = path+"upload"+File.separator;
            log.info(this.getClass().getName()+"臨時儲存圖片路徑saveImgUrl:"+uploadDir);

            File f = new File(uploadDir);
            //如果不存在該路徑就建立
            if (!f.exists()) {
                f.mkdir();
            }
            //獲取檔名
            String uuid= new Date().getTime()+"_"+UUID.randomUUID().toString().
                    replace("-","").substring(0,6);

            //檔案儲存絕對路徑
            String newName = uploadDir+ uuid + "_"+name;

            //上傳檔案位置
            File dir = new File(uploadDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File imgFile = new File(newName);
            //存入臨時記憶體
            FileUtils.writeByteArrayToFile(imgFile, file.getBytes());

            //獲取excel中的資料資訊
            List<Map<String, Object>> maps = ImportExcelFileUtil.getDataFromExcel(newName,typeId == 1 ? new ElPositionDTO() :
                                                                                    typeId == 2 ? new ElUserInfoDTO(): null);
            //刪除臨時儲存的圖片
            if(imgFile.exists() && imgFile.isFile()) {
                imgFile.delete();
            }

            if (CollectionUtils.isEmpty(maps)) {
                log.error("ElAttachmentController的importExcel方法獲取匯入的excel資料為空");
                return  new ResponseResult<>(ExceptionCode.METHOD_FAILURE);
            }

            //獲取的是成功插入的次數
            int row = elAgencyPositionUserService.importBatchData(maps,selAgencyAndPostionVO);
            String result = "";
            if ((maps.size() - row) == 0  ) {
                result = "全部匯入成功"+row+"條";
            } else if ((maps.size() - row) > 0) {
                result ="匯入成功"+row+"條,匯入失敗" + (maps.size() - row) + "條(錯誤或重複)";
            }
            return new ResponseResult(result);
        }catch(BusinessException e){
            log.error("ElAttachmentController的importExcel方法error"+e.getMessage(),e);
            return new ResponseResult<>(e);
        }catch (Exception e) {
            log.error("ElAttachmentController的importExcel異常"+e.getMessage(), e);
            return new ResponseResult(ExceptionCode.INTERFACE_USE_FAILURE);
        }
    }

(2)InportExcelFileUtil類處理excel檔案的資訊。此excel方法是通用的方法

package com.xxx.utils;

import com.xxx.dto.ElUserInfoDTO;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Author xz
 * Date 2018/6/11、9:18
 * Version 1.0
 **/
public class ImportExcelFileUtil {
    private static final Logger log = LoggerFactory.getLogger(ImportExcelFileUtil.class);
    private final static String excel2003L =".xls";    //2003- 版本的excel
    private final static String excel2007U =".xlsx";   //2007+ 版本的excel

    /**
     * 拼裝單個obj  通用
     *
     * @param obj
     * @param row
     * @return
     * @throws Exception
     */
    private  static Map<String, Object> dataObj(Object obj, Row row) throws Exception {
        Class<?> rowClazz= obj.getClass();
        Field[] fields = FieldUtils.getAllFields(rowClazz);
        if (fields == null || fields.length < 1) {
            return null;
        }
        //容器
        Map<String, Object> map = new HashMap<String, Object>();
        //注意excel表格欄位順序要和obj欄位順序對齊 (如果有多餘欄位請另作特殊下標對應處理)
        for (int j = 0; j < fields.length; j++) {
            map.put(fields[j].getName(), getVal(row.getCell(j)));
        }
        return map;
    }
    /**
     * 處理val
     *
     * @param cell
     * @return
     */
    public static String getVal(Cell cell) {
        Object value = null;
        DecimalFormat df = new DecimalFormat("0");  //格式化字元型別的數字
        SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd");  //日期格式化
        DecimalFormat df2 = new DecimalFormat("0.00");  //格式化數字
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_STRING:
                value = cell.getRichStringCellValue().getString();
                break;
            case Cell.CELL_TYPE_NUMERIC:
                if("General".equals(cell.getCellStyle().getDataFormatString())){
                    value = df.format(cell.getNumericCellValue());
                }else if("m/d/yy".equals(cell.getCellStyle().getDataFormatString())){
                    value = sdf.format(cell.getDateCellValue());
                }else{
                    value = df2.format(cell.getNumericCellValue());
                }
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                value = cell.getBooleanCellValue();
                break;
            case Cell.CELL_TYPE_BLANK:
                value = "";
                break;
            default:
                break;
        }
        return value.toString();
    }
    /**
     * * 讀取出filePath中的所有資料資訊
     *
     * @param filePath excel檔案的絕對路徑
     * @param obj
     * @return
     */
    public static List<Map<String, Object>> getDataFromExcel(String filePath, Object obj){

        if (null == obj) {
            return null;
        }
        List<Map<String, Object>> ret = null;
        FileInputStream fis =null;
        Workbook wookbook = null;
        int lineNum = 0;
        Sheet sheet = null;
        try{
            //獲取一個絕對地址的流
            fis = new FileInputStream(filePath);
            wookbook = getWorkbook(fis,filePath);
            //得到一個工作表
            sheet = wookbook.getSheetAt(0);
            //獲得表頭
            Row rowHead = sheet.getRow(0);
            //列數
            int rows = rowHead.getPhysicalNumberOfCells();
            //行數
            lineNum = sheet.getLastRowNum();
            if(0 == lineNum){
                log.info("ImportExcelFileUtil中的getDataFromExcel方法匯入的Excel內沒有資料!");
            }
            ret = getData(sheet, lineNum, rows, obj);
        } catch (Exception e){
            e.printStackTrace();
        }
        return ret;
    }
   


     * @param obj
     * @return
     */
    public static List<Map<String, Object>>  getData(Sheet sheet, int lineNum, int rowNum, Object obj){
        List<Map<String, Object>> ret = null;
        try {
            //容器
            ret = new ArrayList<Map<String, Object>>();
            //獲得所有資料
            for(int i = 1; i <= lineNum; i++){
                //獲得第i行物件
                Row row = sheet.getRow(i);
                if(row!=null){
                    //裝載obj
                    ret.add(dataObj(obj,row));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * 描述:根據檔案字尾,自適應上傳檔案的版本
     *
     * @param inStr,fileName
     * @return
     * @throws Exception
     */
    public static Workbook getWorkbook(InputStream inStr, String fileName) throws Exception{
        Workbook wb = null;
        String fileType = fileName.substring(fileName.lastIndexOf("."));
        if(excel2003L.equals(fileType)){
            wb = new HSSFWorkbook(inStr);  //2003-
        }else if(excel2007U.equals(fileType)){
            wb = new XSSFWorkbook(inStr);  //2007+
        }else{
            throw new Exception("解析的檔案格式有誤!");
        }
        return wb;
    }

    public static void main(String[] args) throws Exception{
        ElUserInfoDTO dto = new ElUserInfoDTO();
        List<Map<String, Object>> dataFromExcel = getDataFromExcel("D:\\img\\測試4.xls", dto);
        for (int i = 0; i < dataFromExcel.size(); i++) {
            for (Map.Entry<String, Object> entry : dataFromExcel.get(i).entrySet()) {
                System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
            }
        }
        System.out.println(dataFromExcel);
    }
}

(3)建立多執行緒,並計算執行緒數,此實現的執行緒是Call,為了可以返回成功的結果

 public int importBatchData(List<Map<String, Object>> list,SelAgencyAndPostionVO selAgencyAndPostionVO) {
        //部門主鍵ID
        Long agencyId = selAgencyAndPostionVO.getAgencyId();
        //型別ID(1:崗位 2:人員 )
        Integer typeId = selAgencyAndPostionVO.getTypeId();
        //崗位主鍵ID
        Long postionId = selAgencyAndPostionVO.getPostionId();

        int row = 0;
        try {
            if (typeId == 1) {
                row = savePositionInfoList(list,agencyId);
            } else if (typeId == 2) {
                Long orgId = elAppInfoService.getOrg().getOrgId();
               //在匯入之前,把同orgId下的使用者全部放進快取,防止重複匯入
                ElUserInfo elUserInfo = new ElUserInfo();
                elUserInfo.setOrgId(orgId);
                List<ElUserInfo> users = userInfoMapper.getUsersByOrgId(elUserInfo);
                //Redis的key值
                String userCodeKey = AppConstants.Flag.USERKEY + orgId;
                //存入Redis之前,把之前的清空
                redisCacheService.deleteRedisCacheByKey(userCodeKey);
                //規則:key==>"userCode"+orgId,value==>users   存入Redis
                redisCacheService.setRedisCacheByKey(userCodeKey,JSON.toJSONString(users),3L,TimeUnit.MINUTES);
                row = saveUserInfoList(list,agencyId,postionId);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return row;
 public int saveUserInfoList(List<Map<String, Object>> list, Long agencyId, Long postionId) {
        Integer row = 1;
        Integer successCount = 0;
        int count = 50;// 一個執行緒處理50條資料
        int listSize = list.size();// 資料集合大小
        int runThreadSize = (listSize / count) + 1; // 開啟的執行緒數
        List<Map<String, Object>> newlist = null;// 存放每個執行緒的執行資料
        ExecutorService executor = Executors.newFixedThreadPool(runThreadSize);// 建立一個執行緒池,數量和開啟執行緒的數量一樣

        // 建立兩個個計數器
        CountDownLatch begin = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(runThreadSize);
        // 迴圈建立執行緒
        for (int i = 0; i < runThreadSize; i++) {
            if ((i + 1) == runThreadSize) {
                int startIndex;
                startIndex = (i * count);
                int endIndex = list.size();
                newlist = list.subList(startIndex, endIndex);
            } else {
                int startIndex = (i * count);
                int endIndex = (i + 1) * count;
                newlist = list.subList(startIndex, endIndex);
            }

            //執行緒類,處理資料持久化
            UserInfoThread userInfoThread = new UserInfoThread(newlist,begin,end,agencyId,postionId);
            //executor.execute(userInfoThread);
            Future<Integer> submit = executor.submit(userInfoThread);
            try {
                //提交成功的次數
                row = submit.get();
                successCount += row;
            } catch (InterruptedException e1) {
                log.error("ElAgencyPositionUserServiceImpl的saveUserInfoList方法error"+e1.getMessage(),e1);
            } catch (ExecutionException e2) {
                log.error("ElAgencyPositionUserServiceImpl的saveUserInfoList方法error"+e2.getMessage(),e2);
            }
        }
        try{
            begin.countDown();
            end.await();
            //執行完關閉執行緒池
            executor.shutdown();
        }catch (Exception e) {
            log.error("ElAgencyPositionUserServiceImpl的saveUserInfoList方法error"+e.getMessage(),e);
        }
        return successCount;
    }

(4)UserInfoThread具體實現業務

package com.xxx.service;

import com.alibaba.fastjson.JSON;
import com.xxx.utils.redis.RedisCacheService;
import com.xxx.bean.ElUserInfo;
import com.xxx.common.AppConstants;
import com.xxx.dao.UserInfoMapper;
import com.xxx.utils.SpringUtil;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;

import static com.xxx.utils.MD5Utils.MD5;

/**
 * Author xz
 * Date 2018/6/11、17:24
 * Version 1.0
 **/
public class UserInfoThread implements Callable<Integer> {

    private static final Logger log = LoggerFactory.getLogger(PostionThread.class);
    private List<Map<String, Object>> list;
    private CountDownLatch begin;
    private CountDownLatch end;
    private Long agencyId;
    private Long postionId;
    private UserInfoMapper userInfoMapper;
    private OrgEmployeeService orgEmployeeService;
    private RedisCacheService redisCacheService;
    private ElAppInfoService elAppInfoService;

    //建立個建構函式初始化 list,和其他用到的引數
    public UserInfoThread(List<Map<String, Object>> list,CountDownLatch begin,CountDownLatch end, Long agencyId, Long postionId){
        this.list = list;
        this.begin = begin;
        this.end = end;
        this.agencyId = agencyId;
        this.postionId = postionId;
        userInfoMapper = (UserInfoMapper)SpringUtil.getBean("userInfoMapper");
        orgEmployeeService = (OrgEmployeeService)SpringUtil.getBean(OrgEmployeeService.class);
        redisCacheService = (RedisCacheService)SpringUtil.getBean(RedisCacheService.class);
        elAppInfoService = (ElAppInfoService)SpringUtil.getBean(ElAppInfoService.class);

    }

    @Override
    public Integer call(){
        int row = 0;
        try {
            List<ElUserInfo>  userList = new ArrayList<ElUserInfo>();
            if (CollectionUtils.isNotEmpty(list)) {
                //組織id
                Long orgId = elAppInfoService.getOrg().getOrgId();
                A:for (int i = 0; i < list.size(); i++) {
                    Map<String, Object> map = list.get(i);
                    String userSex = map.get("userSex").toString().trim();
                    String userName = map.get("userName").toString().trim();
                    String userTel = map.get("userTel").toString().trim();
                    String passWord = map.get("passWord").toString().trim();
                    String key = AppConstants.Flag.USERKEY+orgId;

                    //匯入的人員資訊欄位有一個是為空的,就不持久化資料庫。
                    if (StringUtils.isEmpty(userSex)) {
                        continue;
                    }
                    if (StringUtils.isEmpty(userName)) {
                        continue;
                    }
                    if (StringUtils.isEmpty(userTel)) {
                        continue;
                    }
                    if (StringUtils.isEmpty(passWord)) {
                        continue;
                    }

                    //獲取的是資料庫存在的同orgId下使用者資訊,以json字串形式
                    String userListValue = redisCacheService.getRedisCacheByKey(key);
                    //把json字串轉ElUserInfo使用者物件
                    List<ElUserInfo> elUserInfos = JSON.parseArray(userListValue, ElUserInfo.class);
                    //去重,若有重複的就結束此次迴圈
                    for (ElUserInfo userInfo: elUserInfos) {
                        if (userTel.equals(userInfo.getUserTel())) {
                            continue A;
                        }
                    }

                    if ("男".equals(userSex)) {
                        userSex = "0";
                    } else if ("女".equals(userSex)){
                        userSex = "1";
                    }
                    ElUserInfo user = new ElUserInfo();
                    user.setUserName(userName);
                    user.setUserTel(userTel);
                    user.setPassWord(MD5(passWord));
                    user.setUserSex(userSex);
                    user.setPositionId(postionId);
                    user.setAgencyId(agencyId);
                    user.setCreateDate(new Date());
                    user.setUpdateDate(new Date());
                    user.setDelMark(0);
                    user.setRoleId(1L);
                    user.setEmployeeId(0L);
                    user.setOrgId(orgId);
                    userList.add(user);
                }
                if (CollectionUtils.isNotEmpty(userList)) {
                    //先持久化本地
                    row = userInfoMapper.createBatchUserInfoList(userList);
                    if (row > 0) {
                        //持久化成功後同步組織平臺
                        String add = orgEmployeeService.addOrganRoleUserToPlatform(userList, "add");
                        if (!StringUtils.isEmpty(add) && !"-1".equals(add) && !"null".equals(add)) {
                            //同步成功後,修改OrgId和EmployeeId
                            for (ElUserInfo user : userList) {
                                user.setEmployeeId(1L);
                            }
                            //以使用者手機號碼為唯一標示,批量修改OrgId和EmployeeId
                            userInfoMapper.updateBatchOrgId(userList);
                        }
                        log.info(this.getClass().getName()+"的UserInfoThread"+add.toString());
                    }
                }
            }
            //....
            //執行完讓執行緒直接進入等待
            // begin.await();
        } catch (Exception e) {
            log.error("elPositionInfoServiceImpl的UserInfoThread方法error"+e.getMessage(),e);
        }finally{
            //這裡要主要了,當一個執行緒執行完了計數要減一不要這個執行緒會被一直掛起
            //,end.countDown(),這個方法就是直接把計數器減一的
            end.countDown();
        }
        return row;
    }
}
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    //獲取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通過name獲取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通過class獲取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通過name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

(5)最後,歡迎大家關注【碼農新銳】公眾號,加入我們一起來進階Java。