環境: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); }
結果: