1. 程式人生 > >JAVA匯出資料到excel中大資料量的解決方法——續

JAVA匯出資料到excel中大資料量的解決方法——續

之前寫了個大資料匯入excel的方法,將大資料拆分成多個excel檔案,再打包。有人提出能不能放在一個excel檔案分成多個sheet。後來也寫了實現,一直沒貼出來。

首先介面還是那個介面

import java.io.OutputStream;
import java.util.Collection;

public interface ExportData {

	public void export(final Collection<String> titles, final OutputStream os, final String sql, final Object... sqlParams);
}

只有具體的實現不同
import java.io.IOException;
import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.web.util.HtmlUtils;

import com.avicinfo.common.base.util.StaticMethod;
import com.avicinfo.v2.core.export.ExportData;
import com.cnaec.common.Constant;

/**
 * 將資料匯出到excel,多於指定行數後 放在下一個sheet
 * 
 * @author lisen
 * 
 */
public class ExportData2ExcelSheetImpl implements ExportData {
	/**
	 * 每個檔案的最大行數 超過請求按預設算
	 */
	public static final int MAXROWS = 50000;

	private int maxRow = MAXROWS;
	/**
	 * 用於資料查詢
	 */
	private JdbcTemplate jdbcTemplate;

	StringBuffer head = new StringBuffer("<?xml version=\"1.0\"?>").append(
			"<?mso-application progid=\"Excel.Sheet\"?> ").append(
			"<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" ").append(
			"  xmlns:o=\"urn:schemas-microsoft-com:office:office\" ").append(
			"  xmlns:x=\"urn:schemas-microsoft-com:office:excel\" ").append(
			"  xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" ").append(
			"  xmlns:html=\"http://www.w3.org/TR/REC-html40\">");

	StringBuffer foot = new StringBuffer("</Workbook>");

	StringBuffer sheetHead = new StringBuffer("<Worksheet ss:Name=\"sheet{0}\">").append("<Table>");
	StringBuffer sheetFoot = new StringBuffer("</Table>").append(
			"<WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">").append(
			"<ProtectObjects>False</ProtectObjects>").append("<ProtectScenarios>False</ProtectScenarios>").append(
			"</WorksheetOptions>").append("</Worksheet>");

	public ExportData2ExcelSheetImpl(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 * 獲取單個檔案最大行數
	 * 
	 * @param maxRow
	 * @return
	 */
	protected int getMaxRow() {
		return maxRow < MAXROWS ? maxRow : MAXROWS;
	}

	/**
	 * 資料輸出
	 * 
	 * @param data
	 * @param fos
	 * @throws IOException
	 */
	protected void writeToOutputStream(String data, OutputStream os) throws IOException {
		IOUtils.write(data, os, Constant.ENCODING);
	}

	/**
	 * 檔案頭的寫入
	 * 
	 * @param fos
	 */
	protected void writeHeaderToOutputStream(OutputStream os) throws IOException {
		writeToOutputStream(head.toString(), os);
	}

	/**
	 * 檔案結尾的寫入
	 * 
	 * @param fos
	 */
	protected void writeFooterToOutputStream(OutputStream os) throws IOException {
		writeToOutputStream(foot.toString(), os);
	}

	/**
	 * 檔案頭的寫入
	 * 
	 * @param fos
	 */
	protected void writeSheetHeaderToOutputStream(OutputStream os, int count) throws IOException {
		String sh = sheetHead.toString();
		String head = java.text.MessageFormat.format(sh, count);
		writeToOutputStream(head, os);
	}

	/**
	 * 檔案結尾的寫入
	 * 
	 * @param fos
	 */
	protected void writeSheetFooterToOutputStream(OutputStream os) throws IOException {
		writeToOutputStream(sheetFoot.toString(), os);
	}

	protected void writeTitleToOutputStream(Collection<String> titles, OutputStream os) throws IOException {
		if (titles != null && titles.size() > 0) {
			writeToOutputStream("<Row>", os);
			for (String title : titles) {
				writeToOutputStream("<Cell><Data ss:Type=\"String\">"
						+ (title == null ? "" : HtmlUtils.htmlEscape(title)) + "</Data></Cell>", os);
			}
			writeToOutputStream("</Row>", os);
		}
	}

	protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
		return JdbcUtils.getResultSetValue(rs, index);
	}

	protected void writeOneRowToOutputStream(ResultSet rs, OutputStream os) throws SQLException, IOException {
		// 獲取metaData;
		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();
		writeToOutputStream("<Row>", os);
		for (int i = 1; i <= columnCount; i++) {
			Object obj = getColumnValue(rs, i);
			writeToOutputStream("<Cell><Data ss:Type=\"String\">"
					+ (obj == null ? "" : HtmlUtils.htmlEscape(obj.toString())) + "</Data></Cell>", os);
		}
		writeToOutputStream("</Row>", os);
	}

	public void export(final Collection<String> titles, final Collection<String> firstRow,
			final Collection<String> lastRow, final OutputStream os, String sql, Object... sqlParams) {
		// 每個檔案最大行數
		final int max = getMaxRow();
		List<SqlParameterValue> spvList = new ArrayList<SqlParameterValue>(sqlParams.length);
		for (Object param : sqlParams) {
			SqlParameterValue spv = new SqlParameterValue(JdbcUtils.TYPE_UNKNOWN, param);
			spvList.add(spv);
		}
		PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql, spvList);
		factory.setResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
		PreparedStatementCreator psc = factory.newPreparedStatementCreator(sqlParams);
		PreparedStatementSetter pss = factory.newPreparedStatementSetter(sqlParams);
		jdbcTemplate.query(psc, pss, new ResultSetExtractor() {
			@Override
			public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
				try {
					writeHeaderToOutputStream(os);
					// 行數記錄器
					int i = 0, j = 0;
					while (rs.next()) {
						if (i == 0) {
							// 寫每個sheet頭
							writeSheetHeaderToOutputStream(os, j);
							if (rs.isFirst() && !StaticMethod.isEmpty(firstRow)) {
								writeTitleToOutputStream(firstRow, os);
							}
							// 資料區標題欄
							writeTitleToOutputStream(titles, os);
						}
						// 寫一行
						i++;
						writeOneRowToOutputStream(rs, os);
						if (rs.isLast() && !StaticMethod.isEmpty(lastRow)) {
							writeTitleToOutputStream(lastRow, os);
						}
						if (i == max) {
							i = 0;
							j++;
							// 寫每個sheet尾
							writeSheetFooterToOutputStream(os);
						} else if (rs.isLast()) {
							writeSheetFooterToOutputStream(os);
						}
					}
					writeFooterToOutputStream(os);
				} catch (IOException e) {
					e.printStackTrace();
				}
				return null;
			}
		});
	}

	@Override
	public void export(final Collection<String> titles, final OutputStream os, String sql, Object... sqlParams) {
		// 每個檔案最大行數
		export(titles, null, null, os, sql, sqlParams);
	}

	public void setMaxRow(int maxRow) {
		this.maxRow = maxRow;
	}

}
原理:

excel可以識別出xml格式的檔案,之前是拼成html中的table,這次是按照excel的標準格式拼成xml,這個xml格式由excel另存為後可以獲得。

excel的xml格式中,將不同的sheet以“<Worksheet ss:Name='sheet0'></Worksheet>”標識,sheet中的表為table,行為row,列為cell,單元格中的資料為Data,以此拼出xml後由excel開啟。

我在本機測試匯出123456行資料 用時不到7秒,感覺速度還行