excel動態導入數據庫---mysql

環境:java

    jdk1.8+poi-3.17+mysql-5.6+excel2010(xlsx)mysql

工具類:spring

    1.excel解析工具類,使用poi sax模式解析excel。生成數據格式爲List<LinkedHashMap<String,String>>sql

private List<LinkedHashMap<String,String>> results=new ArrayList<>();
public void process(LinkedHashMap<String, String> dataMap, int curRow) {
        if(headerMap.isEmpty()){
            for(Map.Entry<String, String> e: dataMap.entrySet()){
                if(null != e.getKey() && null != e.getValue()){
                    headerMap.put(e.getKey(), e.getValue().toLowerCase());
                }
            }
        }else{
            LinkedHashMap<String, String> data = new LinkedHashMap<>();
            for (Map.Entry<String, String> e : headerMap.entrySet()) {
                String key = e.getValue();

                String value = null==dataMap.get(e.getKey())?"":dataMap.get(e.getKey());
                data.put(key, value);

            }
            count.getAndIncrement();
            results.add(data);
        }
    }

2.數據庫的excel的配置信息t_fields--能夠配置多個excel數據庫

fname--excel標題字段apache

fcode--標題字段對應的數據庫字段數據結構

data_type--建表字段信息工具

ftable--excel對應的數據表測試

3.excel配置表實體類---jpaurl

package com.fdrx.model;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Created by shea on 2019-06-05.
 */
@Data
@Entity
@Table(name ="t_fields")
public class ExcelBean implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Integer id;
    @Column(length = 10)
    private String fcode;
    @Column(length = 10)
    private String fname;
    @Column(length = 20)
    private String dataType;
    @Column(length = 10)
    private String ftable;
}

package com.fdrx.dao;
import com.fdrx.model.ExcelBean;
import org.springframework.data.jpa.repository.JpaRepository;
/**
 * Created by shea on 2019-06-05.
 */
@Repository
public interface ExcelBeanDao extends JpaRepository<ExcelBean,Integer> {

}

4.excel工具導入服務類

package com.fdrx.service;

import com.fdrx.dao.ExcelBeanDao;
import com.fdrx.model.ExcelBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by shea on 2019-06-06.
 */
@Service
@Slf4j
public class ExcelPipeline {
    @Resource
    private ExcelBeanDao excelBeanDao;
    private HashMap<String,String> allFields= new HashMap<>();
    /**
     * 寫入數據庫
     * @param datas excel解析數據
     * @param table 目標表
     * @return
     */
    public void saveData(List<LinkedHashMap<String, String>> datas,String table){

        //1.獲取配置文件
        List<ExcelBean> all = excelBeanDao.findAll();
        //2.根據表 獲取對應字段映射關係
        allFields=all.stream().filter(m -> table.equals(m.getFtable()))
                .collect(Collectors.toMap(ExcelBean::getFname,ExcelBean::getFcode,(k1, k2)->k2,LinkedHashMap::new));

        //3.根據數據庫配置信息,生成建表sql
        String collect = all.stream().filter(m -> table.equals(m.getFtable()))
                .map(m -> String.format("`%s` %s comment '%s'",m.getFcode(), StringUtils.isEmpty(m.getDataType())?"text":m.getDataType(),m.getFname()))
                .collect(Collectors.joining(","));
        String createSql = String.format("create table IF NOT Exists %s (%s)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", table,collect);

        //4.根據excel標題 獲取導入字段
        List<String> title = createTitle(datas.get(0));
        //5.根據導入字段,生成對應的導入數據列表
        List<List<String>> importLists = convert2List(datas,title);
        //6.根據導入字段,生成導入語句
        String inserSql = createSql(title, table);

        //7.開始msql導入
        Connection con=null;
        try {
            con = getConnector();//jdbc連接
            con.setAutoCommit(false);//不自動提交
            //指定建表語句,存在則不建立
            executeUpdate(createSql,new ArrayList<>(),con);
            for (List<String> strings : importLists) {
                try {
                    executeUpdate(inserSql, strings, con);
                } catch (SQLException e) {
                    log.error("{}=》寫入失敗!",strings.get(0));
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }finally {
            try {
                if(con!=null){
                    con.setAutoCommit(true);
                    con.close();
                }
            } catch (SQLException e) {
                log.error(e.getMessage());
            }
        }
        log.info("導入完成");

    }

    /**
     * 執行 sql 更新
     * @param sql sql
     * @param params params
     * @param conn conn
     * @return
     * @throws SQLException
     */
    private void executeUpdate(String sql, List<String> params, Connection conn ) throws SQLException {
        try (PreparedStatement ps = conn.prepareStatement(sql)){
            int paramIdx = 1;
            for(Object obj: params){
                ps.setObject(paramIdx, obj);
                paramIdx ++;
            }
            ps.executeUpdate();
        }
    }

    /**
     * 根據excel數據生成插入語句
     */
    private String createSql( List<String> title,String table){
        Optional<String> sqlField = title.stream()
                .map(str -> String.format("`%s`", allFields.get(str.trim())))
                .filter(s->!"".equals(s)&& null!=s)
                .reduce((a, b) -> String.format("%s,%s", a, b));
        Optional<String> sqlValue=title.stream()
                .map(str -> String.format("`%s`", allFields.get(str.trim())))
                .filter(s->!"".equals(s)&& null!=s)
                .map(str->"?").reduce((a, b) -> String.format("%s,%s", a, b));
        String sql = String.format("replace into `%s`(%s) values(%s)",table, sqlField.orElse(""), sqlValue.orElse(""));
        return sql;
    }

    /**
     * 映射excel標題行 與 數據庫配置信息===》肯定要導入的數據列
     * @param headers
     * @return
     */
    private List<String> createTitle(LinkedHashMap<String,String> headers){
        return headers.keySet().stream()
                .filter(str -> allFields.get(str.trim())!=null)
                .collect(Collectors.toList());
    }

    /**
     * 轉換數據list
     * @param datas excel數據
     * @param title 導入字段列表
     * @return
     */
    private List<List<String>> convert2List(List<LinkedHashMap<String,String>> datas,List<String> title){
        List<List<String>> collect = datas.stream().map(data -> {
            List<String> single = title.stream().map(data::get).collect(Collectors.toList());
            return single;
        }).collect(Collectors.toList());
        return collect;
    }
    /**
     * jdbc
     * @return
     * @throws SQLException
     */
    public  Connection getConnector() throws SQLException {
        Connection conn = null;
        String url = "jdbc:mysql://localhost:3306/weixin?useUnicode=true&characterEncoding=utf-8";
        String user = "root";
        String passwd = "root";
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url, user, passwd);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return conn;
    }

}

5.測試

package com.fdrx.demo;

import com.fdrx.Application;
import com.fdrx.service.ExcelPipeline;
import com.fdrx.service.JdbcPipeline;
import com.fdrx.util.excel.Excel2007Parser;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * Created by shea on 2019-06-05.
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)//若是須要注入服務,則要啓
@Slf4j
public class TestDemo {

    @Resource
    private JdbcPipeline jdbcPipeline;
    @Resource
    private ExcelPipeline excelPipeline;
    @Test
    public void test1()throws Exception{
        String table ="t_excel2";
        InputStream in = new FileInputStream("C:\\Users\\shea\\Desktop\\excel2.xlsx");
        //調用事件驅動解析excel
        Excel2007Parser parser = new Excel2007Parser(in);
        parser.parse();
        //解析結果
        List<LinkedHashMap<String, String>> datas = parser.getResults();
        excelPipeline.saveData(datas,table);
    }
}

excel導入均可以經過只配置mysql數據表中的excel配置表t_fields便可。

再導入excel的服務類中指定導入excel對應的ftable 便可,程序會自動建立對應的excel表,excel導入就能夠實現快捷導入。

當導入的excel模板發生變化的時候,也能經過t_fields配置,實現標題的動態配置。

 

 

修改成批量插入:不能使用replace into 進行主鍵覆蓋策略,全部須要根據本身業務狀況選擇合適的插入方法

    

#生成插入語句--修改
/**
     * 根據第一行生成插入語句
     */
    private String createSql( List<String> title,String table){
        Optional<String> sqlField = title.stream()
                .map(str -> String.format("`%s`", allFields.get(str.trim())))
                .filter(s->!"".equals(s)&& null!=s)
                .reduce((a, b) -> String.format("%s,%s", a, b));
        String sql = String.format("insert into `%s`(%s) values ",table, sqlField.get());
        return sql;
    }

#生成批量插入
private int batch = 1000;

 String inserSql = createSql(title, table);
        try {

            ArrayList<String> res = new ArrayList<>();
            //使用批量插入---每N條生成一個插入語句
            for (List<String> strings : lists) {

               String s = strings.stream().map(m -> String.format("'%s'", m)).reduce((x,y)->String.join(",",x,y))
                        .map(m -> String.format("(%s)", m)).get();
                res.add(s);
                if(res.size() % batch ==0){
                    String join = String.join(",", res);
                    executeUpdateBatch(inserSql+join, con);
                    res.clear();
                }

            }
            String join = String.join(",", res);
            executeUpdateBatch(inserSql+join, con);

            try {

                if(con!=null){
                    con.setAutoCommit(true);
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

/**
     * 執行 sql批量更新
     * @param sql sql
     * @param conn conn
     * @return
     * @throws SQLException
     */
    private void executeUpdateBatch(String sql, Connection conn ) throws SQLException {
        AtomicInteger count = new AtomicInteger(1);
        PreparedStatement ps = conn.prepareStatement(sql);
       ps.executeUpdate();
       log.info("第{}次提交!",count.getAndIncrement());
    }

 

耗時統計:

 

 

    測試數據:35個字段,共65330條數據

    1.使用單條導入耗時:導入完成,共導入65330條數據,一共耗時24.816秒。

    2.使用批量導入耗時:導入完成,共導入65330條數據,一共耗時7.359秒。

 

新增excel快速導入配置信息到t_fields:

前兩行爲標題行,第三行爲fcode,第四行爲data_type.

你也能夠直接用poi解析,生成 List<ArrayList<String>>的數據便可:第一行爲標題fname,第二行爲字段fcode,第三行爲建表數據格式data_type.

String fieldsTable = "t_fields";//excel字段配置表
        String ftable ="t_excel4";//excel對應的table名稱

        //todo Excel2007Parser解析默認第一行爲標題好,數據從第二行開始計算的,因此excel中多定義一行標題
        InputStream in = new FileInputStream("C:\\Users\\shea\\Desktop\\t4.xlsx");
        Excel2007Parser parser = new Excel2007Parser(in);
        parser.parse();
        //解析結果
        List<LinkedHashMap<String, String>> datas = parser.getResults();

//直接轉換爲須要的數據格式
 List<List<String>> values= datas.get(0).keySet().stream().map(k -> {
            return IntStream.range(0, 3).boxed().map(i -> datas.get(i).get(k)).collect(Collectors.toList());
        }).collect(Collectors.toList());

/**
        //獲取解析結果轉爲list
        List<ArrayList<String>> collect = datas.stream().map(m -> {
            return new ArrayList<>(m.values());
        }).collect(Collectors.toList());

        //實現行轉列--生成插入須要的數據結構
        List<List<String>> values = IntStream.range(0, collect.get(0).size()).boxed().map(i -> {
            return IntStream.range(0, 3).boxed().map(index -> collect.get(index).get(i)).collect(Collectors.toList());

        }).collect(Collectors.toList());
*/
        String sql = String.format("insert into %s (`fname`,`fcode`,`data_type`,`ftable`) values (?,?,?,'%s')",fieldsTable,ftable);
        Connection conn = getcon();
        values.forEach(v-> {
            try {
                executeUpdate(sql,v,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });

根據標題自動生成slq字段---中文縮寫

1.引入拼音處理包

<dependency>
    <groupId>com.hankcs</groupId>
    <artifactId>hanlp</artifactId>
    <version>portable-1.7.2</version>
</dependency>

2.根據中文標題拼音首字母縮寫生成字段

@Test
    public void createTable()throws Exception{
        String fieldsTable = "t_fields";//excel字段配置表
        String ftable ="t_excel6";//excel對應的table名稱
        //todo Excel2007Parser解析默認第一行爲標題好,數據從第二行開始計算的,因此excel中多定義一行標題
        InputStream in = new FileInputStream("C:\\Users\\shea\\Desktop\\t6.xlsx");
        Excel2007Parser parser = new Excel2007Parser(in);
        parser.parse();
        //解析結果
        List<LinkedHashMap<String, String>> datas = parser.getResults();

        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        datas.get(0).keySet().forEach(k->{
            String fcode = HanLP.convertToPinyinFirstCharString(k, "", false);
            if(map.containsKey(fcode)){
                String code = assertLive(map, fcode, 1);
                map.put(code,k);
            }else {
                map.put(fcode,k);
            }
        });

        List<List<String>> values = map.entrySet().stream()
                .map(m -> Stream.of(m.getKey(), m.getValue(), "text").collect(Collectors.toList()))
                .collect(Collectors.toList());

        String sql = String.format("insert into %s (`fcode`,`fname`,`data_type`,`ftable`) values (?,?,?,'%s')",fieldsTable,ftable);

        Connection conn = getcon();
        values.forEach(v-> {
            try {
                executeUpdate(sql,v,conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
        conn.close();


    }

    //遞歸查詢,避免重複字段
    private String assertLive(Map<String,String> map,String code,int index){
        String suffix = String.valueOf(index);
        if(map.containsKey(code.concat(suffix))){
            index++;
            return assertLive(map,code,index);
        }
        return code.concat(suffix);

    }

結果:

相關文章
相關標籤/搜索