Tổng quan về Thiết kế Lớp Cơ sở
Khi triển khai ứng dụng doanh nghiệp dựa trên MyBatis-Plus, việc viết lặp lại các phương thức truy xuất dữ liệu tiêu chuẩn (Insert, Update, Delete, Select) gây tốn thời gian bảo trì. Giải pháp tối ưu là tạo ra một lớp trừu tượng kế thừa, cung cấp các công cụ chung để xử lý phân trang, chuyển đổi dữ liệu và thao tác hàng loạt.
Cấu trúc Dữ liệu Phân trang
Thay vì trả về trực tiếp danh sách kết quả, chúng ta nên đóng gói nó trong một đối tượng chuyên biệt chứa tổng số lượng bản ghi và danh sách mục hiện tại. Điều này giúp giao diện frontend dễ dàng hiển thị thông tin điều hướng.
package com.example.util.pagination;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class PageResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer totalRecords;
private List<T> dataList;
public PageResponse(Integer totalRecords, List<T> dataList) {
this.totalRecords = totalRecords;
this.dataList = dataList;
}
}
Định nghĩa Giao diện Dịch vụ Chung
Để đảm bảo tính nhất quán, mọi Service thực thi đều nên tuân theo một quy ước chung. Giao diện dưới đây định nghĩa các hành động cốt lõi mà không phụ thuộc vào chi tiết của từng Mapper cụ thể.
package com.example.service.base;
import java.io.Serializable;
import java.util.Collection;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
public interface IBaseOperation<T> {
boolean persistSingle(T entity);
boolean persistBatch(Collection<T> entities);
boolean persistBatch(Collection<T> entities, int chunkSize);
boolean modifyById(T entity);
boolean modify(T entity, Wrapper<T> updateCondition);
boolean modifyBatch(Collection<T> entities);
boolean modifyBatch(Collection<T> entities, int chunkSize);
T findById(Serializable id);
boolean removeById(Serializable id);
boolean removeBatch(Collection<? extends Serializable> idList);
}
Cài đặt Lớp Xử lý Chính
Lớp này kế thừa từ các component của MyBatis-Plus nhưng cung cấp các hàm tiện ích mở rộng. Nó bao gồm logic tự động tạo phân trang từ tham số HTTP và xử lý các khối thao tác lớn (batch).
package com.example.service.base.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
public abstract class AbstractServiceImp<M extends BaseMapper<T>, T>
implements IBaseOperation<T> {
@Autowired
protected M mapperInstance;
// Phương thức hỗ trợ tạo đối tượng Page từ Map tham số
protected IPage<T> buildPage(Map<String, Object> queryParams, String defaultSort, boolean ascending) {
int currentPage = 1;
int pageSize = 10;
Object pageNumObj = queryParams.get("page");
if (pageNumObj != null) {
try {
currentPage = Integer.parseInt(String.valueOf(pageNumObj));
} catch (NumberFormatException e) {
currentPage = 1;
}
}
Object limitObj = queryParams.get("size");
if (limitObj != null) {
try {
pageSize = Integer.parseInt(String.valueOf(limitObj));
} catch (NumberFormatException e) {
pageSize = 10;
}
}
Page<T> resultPage = new Page<>(currentPage, pageSize);
queryParams.put("page", resultPage);
String sortField = (String) queryParams.get("sortField");
String sortOrder = (String) queryParams.get("sortOrder");
if (sortField != null && !sortField.isEmpty() && sortOrder != null && !sortOrder.isEmpty()) {
return "asc".equalsIgnoreCase(sortOrder) ? resultPage.setAsc(sortField) : resultPage.setDesc(sortField);
}
return ascending ? resultPage.setAsc(defaultSort) : resultPage.setDesc(defaultSort);
}
// Chuyển đổi danh sách nguồn sang kiểu mong muốn
@SuppressWarnings("unchecked")
protected <V> PageResponse<V> toResponse(List<?> sourceList, long total, Class<V> targetClass) {
List<V> targetTypeList = new ArrayList<>();
sourceList.forEach(item -> targetTypeList.add((V) item));
return new PageResponse<>(Math.toIntExact(total), targetTypeList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean persistSingle(T entity) {
return mapperInstance.insert(entity) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean persistBatch(Collection<T> entities) {
return persistBatch(entities, 500);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean persistBatch(Collection<T> entities, int batchSize) {
SqlSession session = getSqlSession(true);
String insertStmt = getStatement(SqlMethod.INSERT_ONE);
try {
int index = 0;
for (T item : entities) {
session.insert(insertStmt, item);
if (index % batchSize == 0 && index > 0) {
session.flushStatements();
}
index++;
}
session.flushStatements();
return true;
} finally {
closeSession(session);
}
}
@Override
public boolean modifyById(T entity) {
return mapperInstance.updateById(entity) > 0;
}
@Override
public boolean modify(T entity, Wrapper<T> condition) {
return mapperInstance.update(entity, condition) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean modifyBatch(Collection<T> entities) {
return modifyBatch(entities, 500);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean modifyBatch(Collection<T> entities, int batchSize) {
if (entities == null || entities.isEmpty()) return false;
SqlSession session = getSqlSession(true);
String updateStmt = getStatement(SqlMethod.UPDATE_BY_ID);
try {
int counter = 0;
for (T item : entities) {
HashMap<Object, Object> params = new HashMap<>();
params.put("ew", null); // Placeholder for Wrapper
params.put("et", item);
session.update(updateStmt, params);
if (counter % batchSize == 0 && counter > 0) {
session.flushStatements();
}
counter++;
}
session.flushStatements();
return true;
} finally {
closeSession(session);
}
}
@Override
public T findById(Serializable id) {
return mapperInstance.selectById(id);
}
@Override
public boolean removeById(Serializable id) {
return mapperInstance.deleteById(id) > 0;
}
@Override
public boolean removeBatch(Collection<? extends Serializable> ids) {
return mapperInstance.deleteBatchIds(ids) > 0;
}
protected SqlSession getSqlSession(boolean autoCommit) {
return SqlSessionUtils.getSqlSession(mapperInstance.getClass().getSimpleName());
}
protected void closeSession(SqlSession session) {
if (session != null) {
SqlSessionUtils.closeSqlSession(session);
}
}
protected String getStatement(SqlMethod method) {
return mapperInstance.getClass().getName() + "." + method.getMethod();
}
}
Chiến lược Tối ưu Hiệu năng
Mô hình thiết kế trên giải quyết các điểm nghẽn thường gặp:
- Xử lý Batch: Thay vì gọi vòng lặp commit liên tục, việc flush statements theo từng khoảng cách (batch size) giúp giảm tải cho database connection.
- Quản lý Phiên bản: Việc đóng SqlSession trong khối finally ngăn ngừa rò rỉ tài nguyên kết nối.
- Giao diện Rộng: Sử dụng Wrapper thay thế cho tham số trực tiếp giúp linh hoạt hơn trong việc xây dựng điều kiện động.