Nhập dữ liệu lớn
Cấu hình kết nối cơ sở dữ liệu (druid.properties)
Để cấu hình kết nối đến cơ sở dữ liệu MySQL bằng Druid, bạn cần định nghĩa các thuộc tính sau trong tệp druid.properties:
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username=root
password=1234
initialSize=10
maxActive=50
maxWait=60000
Lớp đối tượng dữ liệu (Entity Class)
Định nghĩa một lớp Java để ánh xạ dữ liệu từ bảng cơ sở dữ liệu. Sử dụng annotation của EasyExcel để chỉ định cách ánh xạ cột.
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* Lớp đại diện cho dữ liệu nhập/xuất Excel.
*/
@Data
@TableName("100w_test") // Tên bảng tương ứng trong cơ sở dữ liệu
public class TestPojo {
@ExcelProperty(value = "ID", index = 0) // Ánh xạ cột "ID" tại vị trí 0
private Integer id;
@ExcelProperty(value = "Tên", index = 1) // Ánh xạ cột "Tên" tại vị trí 1
private String name;
@ExcelProperty(value = "Tuổi", index = 2) // Ánh xạ cột "Tuổi" tại vị trí 2
private Integer age;
@ExcelProperty(value = "Ngày sinh", index = 3) // Ánh xạ cột "Ngày sinh" tại vị trí 3
private Integer bir; // Giả định đây là một giá trị số đại diện cho ngày sinh
}
Tiện ích kết nối JDBC (JDBCDruidUtils)
Một lớp tiện ích để quản lý việc kết nối và đóng các tài nguyên JDBC bằng Druid connection pool.
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Tiện ích quản lý kết nối cơ sở dữ liệu sử dụng Druid.
*/
public class JDBCDruidUtils {
private static DataSource dataSource;
// Khối tĩnh để khởi tạo DataSource khi lớp được tải
static {
Properties properties = new Properties();
try (var inputStream = JDBCDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties")) {
if (inputStream == null) {
throw new RuntimeException("Không tìm thấy tệp cấu hình druid.properties");
}
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Lỗi khi khởi tạo Druid DataSource", e);
}
}
/**
* Lấy một kết nối từ pool.
* @return Connection đối tượng kết nối.
* @throws SQLException Nếu có lỗi khi lấy kết nối.
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* Đóng tài nguyên Statement và Connection.
* @param connection Đối tượng Connection cần đóng.
* @param statement Đối tượng Statement cần đóng.
*/
public static void close(Connection connection, Statement statement) {
closeStatement(statement);
closeConnection(connection);
}
/**
* Đóng tài nguyên ResultSet, Statement và Connection.
* @param connection Đối tượng Connection cần đóng.
* @param statement Đối tượng Statement cần đóng.
* @param resultSet Đối tượng ResultSet cần đóng.
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
closeResultSet(resultSet);
close(connection, statement);
}
private static void closeConnection(Connection connection) {
if (connection != null) {
try {
if (!connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void closeStatement(Statement statement) {
if (statement != null) {
try {
if (!statement.isClosed()) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void closeResultSet(ResultSet resultSet) {
if (resultSet != null) {
try {
if (!resultSet.isClosed()) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* Lấy đối tượng DataSource.
* @return DataSource đối tượng DataSource.
*/
public static DataSource getDataSource() {
return dataSource;
}
}
Lớp lắng nghe sự kiện EasyExcel (EasyExcelGeneralDataListener)
Lớp này xử lý từng hàng dữ liệu đọc được từ tệp Excel và lưu trữ theo lô vào cơ sở dữ liệu.
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Listener để xử lý dữ liệu đọc từ Excel bằng EasyExcel.
* Lớp này nhận dữ liệu dưới dạng Map với key là chỉ số cột.
*/
public class EasyExcelGeneralDataListener extends AnalysisEventListener
Thực thi lưu trữ vào cơ sở dữ liệu
Chi tiết về cách xử lý lô dữ liệu và lưu vào cơ sở dữ liệu bằng JDBC với cơ chế transaction.
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Service thực hiện nghiệp vụ nhập dữ liệu từ Excel vào DB.
*/
public class DataImportService { // Đổi tên lớp cho rõ ràng hơn
/**
* Nhập một lô dữ liệu từ Excel vào bảng '100w_test'.
* Sử dụng PreparedStatemnt và addBatch để tối ưu hiệu suất.
* Transaction được quản lý để đảm bảo tính toàn vẹn dữ liệu.
*
* @param dataList Danh sách các bản ghi dữ liệu cần nhập.
* @return Map chứa kết quả thao tác (thành công/thất bại).
*/
public Map<String, Object> importBatchToDB(List<Map<Integer, String>> dataList) {
Map<String, Object> result = new HashMap<>();
if (dataList == null || dataList.isEmpty()) {
result.put("status", "warning");
result.put("message", "Danh sách dữ liệu rỗng, không có gì để nhập.");
return result;
}
Connection connection = null;
PreparedStatement preparedStatement = null;
long startTime = System.currentTimeMillis();
System.out.println("Bắt đầu nhập " + dataList.size() + " bản ghi vào cơ sở dữ liệu.");
try {
connection = JDBCDruidUtils.getConnection();
// Tắt chế độ tự động commit để quản lý transaction thủ công
connection.setAutoCommit(false);
// Câu lệnh SQL với các placeholder
String sql = "INSERT INTO 100w_test (id, name, age, bir) VALUES (?, ?, ?, ?)";
preparedStatement = connection.prepareStatement(sql);
// Duyệt qua danh sách dữ liệu và thêm vào batch
for (Map<Integer, String> record : dataList) {
// Đặt giá trị cho các placeholder. Cần xử lý chuyển đổi kiểu dữ liệu nếu cần.
// Giả định các giá trị trong Map là String và có thể chuyển đổi an toàn.
preparedStatement.setString(1, record.get(0)); // id
preparedStatement.setString(2, record.get(1)); // name
preparedStatement.setString(3, record.get(2)); // age
preparedStatement.setString(4, record.get(3)); // bir
// Thêm câu lệnh vào batch
preparedStatement.addBatch();
}
// Thực thi toàn bộ batch
preparedStatement.executeBatch();
// Commit transaction nếu mọi thứ thành công
connection.commit();
long endTime = System.currentTimeMillis();
System.out.println("Nhập thành công " + dataList.size() + " bản ghi.");
System.out.println("Thời gian thực hiện: " + (endTime - startTime) + " ms");
result.put("status", "success");
result.put("message", "Nhập dữ liệu thành công.");
} catch (SQLException e) {
result.put("status", "error");
result.put("message", "Lỗi khi nhập dữ liệu vào cơ sở dữ liệu.");
e.printStackTrace();
// Rollback transaction nếu có lỗi
if (connection != null) {
try {
connection.rollback();
System.err.println("Transaction đã được rollback.");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} finally {
// Đảm bảo đóng tài nguyên
JDBCDruidUtils.close(connection, preparedStatement);
}
return result;
}
}
Xuất dữ liệu lớn
Controller xử lý yêu cầu xuất dữ liệu
Controller này nhận yêu cầu HTTP GET, truy vấn dữ liệu và ghi ra tệp Excel theo từng phần.
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.ExcelTypeEnum;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import com.alibaba.excel.support.ExcelType;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* Controller để xử lý yêu cầu xuất dữ liệu Excel.
*/
@Controller
public class ExcelExportController {
// Giả định có một Mapper hoặc Repository để truy vấn dữ liệu
@Autowired
private TestPojoMapper testPojoMapper; // Thay thế bằng tên Mapper thực tế của bạn
private static final int SHEET_DATA_ROWS = 1000000; // Số dòng dữ liệu mỗi Sheet
private static final int WRITE_DATA_ROWS = 200000; // Số dòng dữ liệu ghi mỗi lần
@GetMapping("/export-large-data")
public void exportLargeData(HttpServletResponse response) {
OutputStream outputStream = null;
ExcelWriter writer = null;
try {
// Cấu hình response cho việc tải tệp
response.setContentType("multipart/form-data");
response.setCharacterEncoding("utf-8");
String fileName = new String("large_data_export".getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
outputStream = response.getOutputStream();
writer = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX); // Sử dụng ExcelWriter
// Định nghĩa tiêu đề bảng
Table titleTable = new Table();
List<List<String>> titles = new ArrayList<>();
titles.add(Arrays.asList("ID", "Tên", "Tuổi", "Ngày sinh"));
titleTable.setHead(titles);
// Lấy tổng số bản ghi
Integer totalCount = testPojoMapper.selectCount(null); // Giả định có phương thức này
// Tính toán số lượng Sheet và số lần ghi cho mỗi Sheet
int sheetNum = (totalCount + SHEET_DATA_ROWS - 1) / SHEET_DATA_ROWS;
int oneSheetWriteCount = (SHEET_DATA_ROWS + WRITE_DATA_ROWS - 1) / WRITE_DATA_ROWS;
int lastSheetWriteCount = (totalCount % SHEET_DATA_ROWS == 0) ? oneSheetWriteCount :
(totalCount % SHEET_DATA_ROWS + WRITE_DATA_ROWS - 1) / WRITE_DATA_ROWS;
long exportStartTime = System.currentTimeMillis();
System.out.println("Bắt đầu xuất dữ liệu. Tổng số bản ghi: " + totalCount);
List<List<String>> dataRows = new ArrayList<>();
// Vòng lặp ngoài: tạo các Sheet
for (int i = 0; i < sheetNum; i++) {
Sheet sheet = new Sheet(i + 1, i, TestPojo.class); // Tên sheet có thể tùy chỉnh
sheet.setSheetName("Dữ liệu Phần " + (i + 1));
// Xác định số lần ghi cho sheet hiện tại
int currentSheetWriteCount = (i == sheetNum - 1) ? lastSheetWriteCount : oneSheetWriteCount;
// Vòng lặp trong: ghi dữ liệu vào Sheet hiện tại theo từng lô
for (int j = 0; j < currentSheetWriteCount; j++) {
dataRows.clear(); // Xóa dữ liệu cũ để tái sử dụng list
// Tính toán chỉ số trang (page) và kích thước trang (pageSize)
int pageNumber = i * oneSheetWriteCount + j + 1; // Số trang tổng thể
int pageSize = WRITE_DATA_ROWS;
// Sử dụng PageHelper hoặc Mybatis-Plus Paging
Page<TestPojo> page = new Page<>(pageNumber, pageSize);
testPojoMapper.selectPage(page, null); // Truy vấn dữ liệu phân trang
List<TestPojo> records = page.getRecords();
if (!CollectionUtils.isEmpty(records)) {
// Chuyển đổi dữ liệu từ List<TestPojo> sang List<List<String>>
for (TestPojo item : records) {
dataRows.add(Arrays.asList(
String.valueOf(item.getId()),
item.getName(),
String.valueOf(item.getAge()),
String.valueOf(item.getBir())
));
}
}
// Ghi dữ liệu vào sheet
writer.write(dataRows, sheet, titleTable);
}
}
// Hoàn thành ghi và flush
writer.finish();
outputStream.flush();
long exportEndTime = System.currentTimeMillis();
System.out.println("Xuất dữ liệu hoàn tất. Thời gian: " + (exportEndTime - exportStartTime) + " ms");
} catch (IOException e) {
System.err.println("Lỗi I/O khi xuất dữ liệu Excel: " + e.getMessage());
e.printStackTrace();
} finally {
// Đóng các tài nguyên
if (writer != null) {
writer.close();
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// Interface giả định cho Mybatis Mapper
public interface TestPojoMapper {
Integer selectCount(Object queryWrapper);
void selectPage(Page<TestPojo> page, Object queryWrapper);
}
}