1. 程式人生 > >資料來源為Excel的解決方法

資料來源為Excel的解決方法

        上次做專案時要求資料來源改為Excel,與專案一起打包執行。查了一下資料來源是否可以直接使用Excel的解決方案,雖然有提供可以連線Excel的驅動,但是以Excel直接操作資料實在是不方便,沒有資料庫的功能強大,因此想到了使用了內嵌式資料庫,再編寫一段上傳Excel把資料更新到資料庫的程式碼,即可同樣達到資料來源與專案打包的效果。

一、解決思路

要求:資料來源為Excel,與專案一起打包

方法:使用內嵌式資料庫,後臺處理上傳Excel資料至資料庫

內嵌式資料庫就是不需要像Mysql/Oracle一樣安裝啟動服務,而是在專案中直接引用它的jar包即可,它會以檔案的形式存在於資料庫中。在此專案中我使用了sqlite3。

上傳Excel資料至資料庫這一塊我使用了fileUpload和poi,前者是用於上傳檔案,後者用於從Excel中讀取資料;資料庫連線我採用了SpringJdbcTemplate。好啦,以下是詳細的步驟。

二、詳細步驟

1、安裝內建資料庫sqlite3

1)首先從官網下載,我是在Windows下開發的,雖然後續是在linux環境下執行,但是sqlite3它可以自己跨平臺,所以我直接下載windows版本就好了。

2)解壓,然後建立一個sqlite3型別的檔案,在命令提示符中使用sqlite3 DatabaseName.db的命令即可在當前目錄下建立一個sqlite3型別的資料庫

3)你可以用Navicat等視覺化工具連線資料庫,也可以把資料庫直接複製到專案中,然後在applicationContext配置檔案中配置連線資訊即可。我這裡採用了C3P0連線池。如果是放在resources下面,URL可以像我這樣寫,另外sqlite3的所使用的驅動是org.sqlite.JDBC。使用者名稱和密碼可以不使用。

測試一下,可發現數據庫能夠正常連線

2、Excel讀取資料並寫入資料庫

poi提供了Excel讀取的瞭解決方案,有一點要注意的是,分為HSSF和XSSF,其中HSSF是針對的office2007之前的版本xls,而XSSF針對的是office2007以後的版本xlsx,我這裡採用的是XSSF。

思路:上傳檔案存放至臨時地點——讀取Excel資料封裝成List——清空資料庫之前的資料——插入資料到資料庫

讀取Excel資料封裝成List,主要思想就是遍歷每個sheet,再在每個sheet中遍歷行,取列值,每行封裝成一個物件,每個sheet又封裝成一個list,批量插入資料庫,下面直接上程式碼。

讀取Excel.java

public class UploadExcel {
    public static List<Ammeter> readXlsx(String path) throws Exception {
        List<Ammeter> list = new ArrayList<Ammeter>();
        InputStream is = new FileInputStream(path);
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
        for (Sheet xssfSheet : xssfWorkbook) {
            if (xssfSheet == null) {
                continue;
            }
            for (int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
                Row xssfRow = xssfSheet.getRow(rowNum);
                if (xssfRow == null) {
                    continue;
                }
                List<Ammeter> rowList = new ArrayList<Ammeter>();
                Ammeter ammeter = new Ammeter();
                int len=0;

                Cell cell = xssfRow.getCell(0);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }
                ammeter.setTabNumber(cell.getStringCellValue());
                cell = xssfRow.getCell(1);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }if((len=cell.getStringCellValue().length())>6){
                    ammeter.setActivePower(cell.getStringCellValue().substring(0,6));
                }else{
                    ammeter.setActivePower(cell.getStringCellValue());
                }

                cell = xssfRow.getCell(2);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }if((len=cell.getStringCellValue().length())>6){
                    ammeter.setReactivePower(cell.getStringCellValue().substring(0,6));
                }else{
                    ammeter.setReactivePower(cell.getStringCellValue());
                }

                cell = xssfRow.getCell(3);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }if((len=cell.getStringCellValue().length())>6){
                    ammeter.setCumulativePower(cell.getStringCellValue().substring(0,6));
                }else{
                    ammeter.setCumulativePower(cell.getStringCellValue());
                }

                cell = xssfRow.getCell(4);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }if((len=cell.getStringCellValue().length())>6){
                    ammeter.setVoltage(cell.getStringCellValue().substring(0,6));
                }else{
                    ammeter.setVoltage(cell.getStringCellValue());
                }

                cell = xssfRow.getCell(5);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }if((len=cell.getStringCellValue().length())>6){
                    ammeter.setElectricity(cell.getStringCellValue().substring(0,6));
                }else{
                    ammeter.setElectricity(cell.getStringCellValue());
                }

                cell = xssfRow.getCell(6);
                if (cell != null) {
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                }
                ammeter.setCurrentTime(cell.getStringCellValue());

                list.add(ammeter);
            }
        }
        return list;


    }
}

這裡有幾個點要注意一下:

1)我直接跟業務耦合了,但其實可以抽取出來作為一個工具類的;

2)讀取的時候我直接將Excel單元格的型別強制轉換為字串型別,使用的cell.setCellType(Cell.CELL_TYPE_STRING),但是這個方法在新版的poi中的已經棄用了的,後續再看抽象出一個方法類的時候再寫,現在先過;

3)Excel中使用下拉填充的數字讀取出來有時會帶若干小數,在這裡我因為強制轉換為字元型別,所以採用的subString進行擷取,可以使用保留小數後再轉換為字串;如果使用我的subString的方法的話要先判斷下字串長度,避免產生index溢位的錯誤。

清空資料庫Dao.java

 //清空資料庫
    public void truncate(){
        String sql = "delete from ammeter";
        jdbcTemplate.execute(sql);
    }

這裡要注意的幾個點:

1)mysql中可以採用truncate table tableName進行清空資料庫,但是sqlite3裡是不支援這樣的命令的,只能使用delete from tabeName命令,但是這個命令是不能刪除自增ID的,即假如你有100條資料,雖然清空掉了,但是插入資料的ID是從101開始的;

2)在JdbcTemplate裡,execute()方法可用於執行任何sql語句,但是一般用來執行DDL語句。

插入資料庫Dao.java

//插入資料到資料庫
    public void insert(List<Ammeter> ammeters){
        final List<Ammeter> tempAmmeter=ammeters;
        String sql = "insert into ammeter(tabNumber,activePower,reactivePower,cumulativePower,electricity,voltage,currentTime) values (?,?,?,?,?,?,?)";
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                String tabNumber=tempAmmeter.get(i).getTabNumber();
                String activePower=tempAmmeter.get(i).getActivePower();
                String reactivePower=tempAmmeter.get(i).getReactivePower();
                String cumulativePower=tempAmmeter.get(i).getCumulativePower();
                String electricity=tempAmmeter.get(i).getElectricity();
                String voltage=tempAmmeter.get(i).getVoltage();
                String currentTime=tempAmmeter.get(i).getCurrentTime();
                ps.setString(1, tabNumber);
                ps.setString(2, activePower);
                ps.setString(3, reactivePower);
                ps.setString(4, cumulativePower);
                ps.setString(5, electricity);
                ps.setString(6, voltage);
                ps.setString(7, currentTime);
            }

            @Override
            public int getBatchSize() {
                return tempAmmeter.size();
            }
        });
    }

這裡要注意的幾個點:

1)採用了JdbcTemplate的批量插入方法batchUpdate(),一般來說update()方法用來執行增加、修改和刪除等語句,batchUpdate()方法用來執行批處理相關的語句;這裡有個疑問,雖然我使用了該批量插入方法,但是在執行5500多條資料時扔用了3-5min時間,具體原因暫未知;

2)如果是想要批量更新資料的話,可以採用臨時資料庫,先上傳資料到臨時資料庫,再根據外來鍵更新正式資料庫,這樣速度會快點。

3、客戶端上傳Excel

思路:瀏覽器上傳Excel——存入專案的某個路徑下——UploadExcel讀取該路徑下的Excel——執行操作

由於牽涉到前端頁面,所以採用servlet和jsp;servet中用到了spring框架;上傳檔案的存放位置採用了相對路徑,不能寫死,避免了平臺問題。直接上程式碼。

上傳按鈕.jsp

<form action="${pageContext.request.contextPath}/servlet/UploadHandleServlet" method="post" enctype="multipart/form-data" >
    請選擇上傳檔案<input type="file" name="file"><br>
    <input type="submit" value="上傳">
</form>

處理上傳Servlet.java

@Controller
public class UploadHandleServlet extends HttpServlet {
    @Autowired
    private AmmeterDao ammeterDao;
    public void init() throws ServletException {
        /**
         * 利用init方法來呼叫Spring容器BeanFactory
         * 看看UserServlet是否能夠通過Spring容器獲取物件
         */
        WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); //通過Web容器去得到BeanFactory物件
        AutowireCapableBeanFactory autowireCapableBeanFactory = wc.getAutowireCapableBeanFactory();
        autowireCapableBeanFactory.autowireBean(this);
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File file = new File(savePath);
        if(!file.exists()&&!file.isDirectory()){
            System.out.println(savePath+"目錄不存在,需要建立");
            file.mkdir();
        }
        String message = "上傳成功";
        try {
            DiskFileItemFactory diskfactory = new DiskFileItemFactory();//建立工廠類物件
            ServletFileUpload fileUpload = new ServletFileUpload(diskfactory);//使用工廠建立解析器物件
            if(!ServletFileUpload.isMultipartContent(request)){
                return;
            }
            List<FileItem> fileItems = fileUpload.parseRequest(request);
            for(FileItem item:fileItems) {
                if(item.isFormField()) {
                    System.out.println(new String(item.getString().getBytes("ISO-8859-1"),"utf-8"));
                } else{
                    item.write(new File(savePath+"\\"+"temp.xlsx"));
                    System.out.println(message);
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        List<Ammeter> temp= null;
        try {
            temp = UploadExcel.readXlsx(savePath+"\\"+"temp.xlsx");
            System.out.println(temp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ammeterDao.truncate();
        ammeterDao.insert(temp);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

這裡要注意的幾個點:

1)由於Dao是使用了Spring框架用Autowired自動裝配的,所以使用new是不會呼叫的,要想在servlet裡使用自動裝配,需要利用init方法來呼叫Spring容器BeanFactory,然後使用@Autowired自動裝配;

2)使用fileupload的jar包。同時要有io

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.1</version>
    </dependency>

3)不要把路徑寫死,要用相對路徑; this.getServletContext().getRealPath("/WEB-INF/upload");這個路徑指的是專案執行的真實路徑,到時會在執行的Tomcat伺服器下的webapps\ROOT\WEB-INF\upload產生檔案;而且指定了檔名後覆蓋,不用擔心儲存問題;

以上。