MyBatis-Plus实战案例:从零构建电商系统
本文详细介绍了如何使用MyBatis-Plus构建完整的电商系统,涵盖数据库设计与建模、商品模块CRUD操作、订单分页查询与复杂条件处理、系统性能优化与监控方案等核心内容。通过具体的代码示例和最佳实践,展示了MyBatis-Plus在电商系统中的强大功能和性能优势。
电商系统数据库设计与建模
在构建电商系统时,合理的数据库设计是系统稳定性和性能的基石。MyBatis-Plus作为MyBatis的增强工具,提供了强大的ORM功能和便捷的CRUD操作,能够显著提升开发效率。本节将详细介绍如何使用MyBatis-Plus进行电商系统的数据库设计与建模。
电商核心实体设计
电商系统通常包含用户、商品、订单、购物车等核心实体。让我们通过实体类定义来展示如何利用MyBatis-Plus的注解进行建模。
用户实体设计
@TableName("t_user")
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("username")
private String username;
@TableField(value = "password", updateStrategy = FieldStrategy.NOT_EMPTY)
private String password;
@TableField("email")
private String email;
@TableField("phone")
private String phone;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
}
商品实体设计
@TableName("t_product")
@Data
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("product_name")
private String productName;
@TableField("product_desc")
private String productDesc;
@TableField("price")
private BigDecimal price;
@TableField("stock")
private Integer stock;
@TableField("category_id")
private Long categoryId;
@TableField("status")
private Integer status;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
数据库表关系设计
电商系统的表关系设计需要充分考虑业务需求和性能优化。以下是主要表之间的关系:
MyBatis-Plus注解详解
在电商系统建模中,MyBatis-Plus提供了丰富的注解来简化开发:
主键策略配置
MyBatis-Plus支持多种主键生成策略,电商系统推荐使用分布式ID:
public enum IdType {
AUTO(0), // 数据库自增
NONE(1), // 无状态
INPUT(2), // 用户输入
ASSIGN_ID(3), // 分配ID(雪花算法)
ASSIGN_UUID(4)// 分配UUID
}
电商系统推荐使用 ASSIGN_ID,基于雪花算法生成分布式唯一ID。
字段填充策略
自动填充功能在电商系统中非常实用:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
逻辑删除配置
电商系统需要支持数据软删除:
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDeleted
logic-delete-value: 1
logic-not-delete-value: 0
复杂业务场景建模
订单实体与关联设计
@TableName("t_order")
@Data
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("order_no")
private String orderNo;
@TableField("user_id")
private Long userId;
@TableField("total_amount")
private BigDecimal totalAmount;
@TableField("status")
private Integer status;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
// 订单项列表(一对多关系)
@TableField(exist = false)
private List<OrderItem> orderItems;
}
@TableName("t_order_item")
@Data
public class OrderItem implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("order_id")
private Long orderId;
@TableField("product_id")
private Long productId;
@TableField("product_name")
private String productName;
@TableField("product_price")
private BigDecimal productPrice;
@TableField("quantity")
private Integer quantity;
@TableField("subtotal")
private BigDecimal subtotal;
}
购物车设计
@TableName("t_cart")
@Data
public class Cart implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("user_id")
private Long userId;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
// 购物车项列表
@TableField(exist = false)
private List<CartItem> cartItems;
}
@TableName("t_cart_item")
@Data
public class CartItem implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("cart_id")
private Long cartId;
@TableField("product_id")
private Long productId;
@TableField("quantity")
private Integer quantity;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
索引优化策略
合理的索引设计对电商系统性能至关重要:
| 表名 | 索引字段 | 索引类型 | 说明 |
|---|---|---|---|
| t_user | username | UNIQUE | 用户名唯一索引 |
| t_user | phone | UNIQUE | 手机号唯一索引 |
| t_product | category_id | INDEX | 分类查询索引 |
| t_product | status | INDEX | 状态查询索引 |
| t_order | user_id | INDEX | 用户订单查询 |
| t_order | create_time | INDEX | 时间范围查询 |
数据字典设计
电商系统需要统一的状态管理:
public class OrderStatus {
public static final int UNPAID = 0; // 待支付
public static final int PAID = 1; // 已支付
public static final int SHIPPED = 2; // 已发货
public static final int COMPLETED = 3; // 已完成
public static final int CANCELLED = 4; // 已取消
}
public class ProductStatus {
public static final int ON_SALE = 1; // 上架中
public static final int OFF_SALE = 0; // 已下架
}
性能优化建议
- 分表分库策略:对于订单等高频写入表,建议按用户ID进行分表
- 读写分离:配置MyBatis-Plus的多数据源支持读写分离
- 缓存策略:对商品信息等读多写少的数据使用Redis缓存
- 批量操作:利用MyBatis-Plus的批量操作方法提升性能
通过合理的数据库设计和MyBatis-Plus的强大功能,可以构建出高性能、易维护的电商系统数据库架构。这种设计不仅满足了业务需求,还为系统的扩展和优化提供了良好的基础。
商品模块的CRUD操作实现
在电商系统中,商品模块是最核心的基础模块之一。MyBatis-Plus提供了强大的CRUD操作支持,让我们能够快速、高效地实现商品数据的增删改查功能。本节将详细介绍如何使用MyBatis-Plus实现商品模块的完整CRUD操作。
商品实体类设计
首先,我们需要设计商品实体类,使用MyBatis-Plus的注解来映射数据库表结构:
@Data
@TableName("product")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
@TableField("product_name")
private String productName;
private BigDecimal price;
private Integer stock;
private String description;
@TableField("category_id")
private Long categoryId;
@TableField("create_time")
private Date createTime;
@TableField("update_time")
private Date updateTime;
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
}
Mapper接口定义
继承BaseMapper接口,无需编写XML文件即可获得完整的CRUD功能:
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
// 自定义查询方法
List<Product> selectByCategory(@Param("categoryId") Long categoryId);
// 分页查询
IPage<Product> selectPageByCondition(IPage<Product> page,
@Param("condition") ProductQueryCondition condition);
}
Service层实现
Service层封装业务逻辑,提供完整的商品管理功能:
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
/**
* 新增商品
*/
public boolean saveProduct(Product product) {
product.setCreateTime(new Date());
product.setUpdateTime(new Date());
return productMapper.insert(product) > 0;
}
/**
* 批量新增商品
*/
public boolean saveBatchProducts(List<Product> products) {
products.forEach(product -> {
product.setCreateTime(new Date());
product.setUpdateTime(new Date());
});
return productMapper.insertBatchSomeColumn(products) > 0;
}
/**
* 根据ID查询商品
*/
public Product getProductById(Long id) {
return productMapper.selectById(id);
}
/**
* 条件查询商品列表
*/
public List<Product> listProducts(ProductQueryCondition condition) {
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(condition.getProductName())) {
wrapper.like(Product::getProductName, condition.getProductName());
}
if (condition.getCategoryId() != null) {
wrapper.eq(Product::getCategoryId, condition.getCategoryId());
}
if (condition.getMinPrice() != null) {
wrapper.ge(Product::getPrice, condition.getMinPrice());
}
if (condition.getMaxPrice() != null) {
wrapper.le(Product::getPrice, condition.getMaxPrice());
}
wrapper.orderByDesc(Product::getCreateTime);
return productMapper.selectList(wrapper);
}
/**
* 分页查询商品
*/
public IPage<Product> pageProducts(Page<Product> page, ProductQueryCondition condition) {
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(condition.getProductName())) {
wrapper.like(Product::getProductName, condition.getProductName());
}
if (condition.getCategoryId() != null) {
wrapper.eq(Product::getCategoryId, condition.getCategoryId());
}
wrapper.orderByDesc(Product::getCreateTime);
return productMapper.selectPage(page, wrapper);
}
/**
* 更新商品信息
*/
public boolean updateProduct(Product product) {
product.setUpdateTime(new Date());
return productMapper.updateById(product) > 0;
}
/**
* 条件更新商品
*/
public boolean updateProductByCondition(Product product, ProductUpdateCondition condition) {
LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<>();
if (condition.getCategoryId() != null) {
wrapper.eq(Product::getCategoryId, condition.getCategoryId());
}
if (condition.getMinStock() != null) {
wrapper.ge(Product::getStock, condition.getMinStock());
}
return productMapper.update(product, wrapper) > 0;
}
/**
* 删除商品(逻辑删除)
*/
public boolean deleteProduct(Long id) {
return productMapper.deleteById(id) > 0;
}
/**
* 批量删除商品
*/
public boolean deleteBatchProducts(List<Long> ids) {
return productMapper.deleteBatchIds(ids) > 0;
}
/**
* 调整商品库存
*/
public boolean adjustStock(Long productId, Integer quantity) {
LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Product::getId, productId)
.setSql("stock = stock + " + quantity);
return productMapper.update(null, wrapper) > 0;
}
}
Controller层实现
提供RESTful API接口:
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public Result<Boolean> createProduct(@RequestBody Product product) {
boolean success = productService.saveProduct(product);
return Result.success(success);
}
@GetMapping("/{id}")
public Result<Product> getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
return Result.success(product);
}
@GetMapping
public Result<List<Product>> listProducts(ProductQueryCondition condition) {
List<Product> products = productService.listProducts(condition);
return Result.success(products);
}
@GetMapping("/page")
public Result<IPage<Product>> pageProducts(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
ProductQueryCondition condition) {
Page<Product> page = new Page<>(current, size);
IPage<Product> result = productService.pageProducts(page, condition);
return Result.success(result);
}
@PutMapping("/{id}")
public Result<Boolean> updateProduct(@PathVariable Long id, @RequestBody Product product) {
product.setId(id);
boolean success = productService.updateProduct(product);
return Result.success(success);
}
@DeleteMapping("/{id}")
public Result<Boolean> deleteProduct(@PathVariable Long id) {
boolean success = productService.deleteProduct(id);
return Result.success(success);
}
@PatchMapping("/{id}/stock")
public Result<Boolean> adjustStock(@PathVariable Long id, @RequestParam Integer quantity) {
boolean success = productService.adjustStock(id, quantity);
return Result.success(success);
}
}
查询条件封装
定义查询条件类,便于参数传递:
@Data
public class ProductQueryCondition {
private String productName;
private Long categoryId;
private BigDecimal minPrice;
private BigDecimal maxPrice;
private Integer minStock;
private Integer maxStock;
private Date createTimeStart;
private Date createTimeEnd;
}
@Data
public class ProductUpdateCondition {
private Long categoryId;
private Integer minStock;
private Integer maxStock;
}
高级查询示例
MyBatis-Plus提供了丰富的查询构建功能:
// 复杂条件查询示例
public List<Product> complexQuery(ProductQueryCondition condition) {
LambdaQueryWrapper<Product> wrapper = Wrappers.lambdaQuery();
// 商品名称模糊查询
if (StringUtils.isNotBlank(condition.getProductName())) {
wrapper.like(Product::getProductName, condition.getProductName());
}
// 价格范围查询
if (condition.getMinPrice() != null && condition.getMaxPrice() != null) {
wrapper.between(Product::getPrice, condition.getMinPrice(), condition.getMaxPrice());
} else if (condition.getMinPrice() != null) {
wrapper.ge(Product::getPrice, condition.getMinPrice());
} else if (condition.getMaxPrice() != null) {
wrapper.le(Product::getPrice, condition.getMaxPrice());
}
// 库存查询
if (condition.getMinStock() != null) {
wrapper.ge(Product::getStock, condition.getMinStock());
}
// 分类查询
if (condition.getCategoryId() != null) {
wrapper.eq(Product::getCategoryId, condition.getCategoryId());
}
// 时间范围查询
if (condition.getCreateTimeStart() != null && condition.getCreateTimeEnd() != null) {
wrapper.between(Product::getCreateTime, condition.getCreateTimeStart(), condition.getCreateTimeEnd());
}
// 排序和分页
wrapper.orderByDesc(Product::getCreateTime, Product::getPrice);
return productMapper.selectList(wrapper);
}
批量操作优化
MyBatis-Plus提供了高效的批量操作支持:
// 批量插入优化
public boolean batchInsertProducts(List<Product> products) {
// 使用MyBatis-Plus的批量插入方法
return productMapper.insertBatchSomeColumn(products) > 0;
}
// 批量更新
public boolean batchUpdateProducts(List<Product> products) {
// 使用事务确保批量操作的原子性
return transactionTemplate.execute(status -> {
for (Product product : products) {
product.setUpdateTime(new Date());
if (productMapper.updateById(product) <= 0) {
status.setRollbackOnly();
return false;
}
}
return true;
});
}
数据统计与聚合查询
// 商品统计信息
public ProductStatistics getProductStatistics() {
ProductStatistics statistics = new ProductStatistics();
// 商品总数
statistics.setTotalCount(productMapper.selectCount(Wrappers.emptyWrapper()));
// 分类商品数量统计
List<Map<String, Object>> categoryStats = productMapper.selectMaps(
Wrappers.query()
.select("category_id", "COUNT(*) as count")
.groupBy("category_id")
);
// 价格区间统计
List<Map<String, Object>> priceRangeStats = productMapper.selectMaps(
Wrappers.query()
.select("CASE " +
"WHEN price < 100 THEN '0-100' " +
"WHEN price < 500 THEN '100-500' " +
"WHEN price < 1000 THEN '500-1000' " +
"ELSE '1000+' END as price_range, " +
"COUNT(*) as count")
.groupBy("price_range")
);
return statistics;
}
通过上述实现,我们构建了一个完整的商品模块CRUD系统。MyBatis-Plus的强大功能让我们能够:
- 快速开发:基于BaseMapper的自动CRUD方法
- 灵活查询:LambdaQueryWrapper提供类型安全的查询条件构建
- 高效分页:内置分页插件支持
- 批量操作:优化的批量插入和更新
- 逻辑删除:@TableLogic注解实现软删除
- 条件更新:LambdaUpdateWrapper支持复杂更新条件
这种实现方式不仅代码简洁,而且性能优异,非常适合电商系统的高并发场景。
订单分页查询与复杂条件处理
在电商系统的开发中,订单管理是一个核心模块,经常需要处理大量的订单数据查询。MyBatis-Plus提供了强大的分页查询和复杂条件处理能力,能够帮助我们高效地实现各种订单查询需求。
分页查询基础
MyBatis-Plus的分页功能基于IPage接口和Page类实现,支持自动统计总数和多种数据库方言。
基本分页查询
// 创建分页对象,查询第1页,每页10条
Page<Order> page = new Page<>(1, 10);
// 执行分页查询
IPage<Order> orderPage = orderMapper.selectPage(page, null);
// 获取分页数据
List<Order> orders = orderPage.getRecords(); // 当前页数据
long total = orderPage.getTotal(); // 总记录数
long pages = orderPage.getPages(); // 总页数
分页查询流程
复杂条件查询
MyBatis-Plus提供了强大的条件构造器,支持各种复杂的查询条件组合。
基本条件构造器
// 创建查询条件
QueryWrapper<Order> wrapper = new QueryWrapper<>();
// 等于条件
wrapper.eq("order_status", 1); // 订单状态为1
wrapper.eq("user_id", 1001); // 用户ID为1001
// 范围查询
wrapper.between("create_time",
LocalDateTime.of(2024, 1, 1, 0, 0),
LocalDateTime.of(2024, 12, 31, 23, 59)); // 2024年内的订单
// 模糊查询
wrapper.like("order_no", "2024"); // 订单号包含2024
// 执行分页查询
Page<Order> page = new Page<>(1, 10);
IPage<Order> result = orderMapper.selectPage(page, wrapper);
Lambda表达式条件构造器
使用Lambda表达式可以避免硬编码字段名,提高代码的可维护性:
LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery();
wrapper.eq(Order::getOrderStatus, 1) // 订单状态为1
.eq(Order::getUserId, 1001) // 用户ID为1001
.between(Order::getCreateTime,
LocalDateTime.of(2024, 1, 1, 0, 0),
LocalDateTime.of(2024, 12, 31, 23, 59))
.like(Order::getOrderNo, "2024"); // 订单号包含2024
Page<Order> page = new Page<>(1, 10);
IPage<Order> result = orderMapper.selectPage(page, wrapper);
高级查询技巧
多条件组合查询
在实际业务中,经常需要处理复杂的多条件组合查询:
LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery();
// 基础条件
wrapper.eq(Order::getDeleted, 0) // 未删除的订单
.ge(Order::getOrderAmount, 100.00); // 订单金额大于等于100
// 复杂条件组合:AND/OR嵌套
wrapper.and(w -> w
.eq(Order::getOrderStatus, 1) // 状态为1(待付款)
.or()
.eq(Order::getOrderStatus, 2) // 或状态为2(已付款)
)
.nested(w -> w
.like(Order::getOrderNo, "2024") // 订单号包含2024
.or()
.like(Order::getProductName, "手机") // 或商品名称包含手机
);
// 排序
wrapper.orderByDesc(Order::getCreateTime) // 按创建时间倒序
.orderByAsc(Order::getId); // 按ID升序
Page<Order> page = new Page<>(1, 20);
IPage<Order> result = orderMapper.selectPage(page, wrapper);
动态条件查询
根据前端传入的参数动态构建查询条件:
public IPage<Order> searchOrders(OrderQueryDTO queryDTO) {
LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery();
// 基础条件
wrapper.eq(Order::getDeleted, 0);
// 动态条件
if (StringUtils.isNotBlank(queryDTO.getOrderNo())) {
wrapper.like(Order::getOrderNo, queryDTO.getOrderNo());
}
if (queryDTO.getOrderStatus() != null) {
wrapper.eq(Order::getOrderStatus, queryDTO.getOrderStatus());
}
if (queryDTO.getMinAmount() != null && queryDTO.getMaxAmount() != null) {
wrapper.between(Order::getOrderAmount,
queryDTO.getMinAmount(), queryDTO.getMaxAmount());
}
if (queryDTO.getStartTime() != null && queryDTO.getEndTime() != null) {
wrapper.between(Order::getCreateTime,
queryDTO.getStartTime(), queryDTO.getEndTime());
}
// 排序处理
if (StringUtils.isNotBlank(queryDTO.getSortField())) {
if ("asc".equalsIgnoreCase(queryDTO.getSortOrder())) {
wrapper.orderByAsc(getOrderField(queryDTO.getSortField()));
} else {
wrapper.orderByDesc(getOrderField(queryDTO.getSortField()));
}
} else {
wrapper.orderByDesc(Order::getCreateTime);
}
Page<Order> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
return orderMapper.selectPage(page, wrapper);
}
private SFunction<Order, ?> getOrderField(String fieldName) {
switch (fieldName) {
case "createTime": return Order::getCreateTime;
case "orderAmount": return Order::getOrderAmount;
case "orderStatus": return Order::getOrderStatus;
default: return Order::getId;
}
}
性能优化技巧
分页查询优化
// 1. 禁用COUNT查询(当不需要总数时)
Page<Order> page = new Page<>(1, 10, false); // searchCount = false
// 2. 优化COUNT SQL(避免JOIN的性能问题)
page.setOptimizeCountSql(true); // 默认已开启
// 3. 设置最大分页条数限制
page.setMaxLimit(100L); // 单页最多100条
// 4. 自定义COUNT查询(复杂查询时)
page.setCountId("OrderMapper.customCountQuery"); // 指定自定义COUNT方法
索引优化建议
为了确保分页查询的性能,数据库表应该建立合适的索引:
| 查询条件 | 推荐索引 | 说明 |
|---|---|---|
| 用户ID + 状态 | idx_user_status | 用户订单列表查询 |
| 创建时间范围 | idx_create_time | 时间范围查询 |
| 订单状态 | idx_order_status | 状态筛选 |
| 订单号 | uk_order_no | 订单号唯一索引 |
复杂业务场景示例
多表关联分页查询
public IPage<OrderVO> searchOrderWithDetail(OrderQueryDTO queryDTO) {
Page<Order> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
// 使用QueryWrapper进行复杂关联查询
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.select("o.*", "u.username", "p.product_name")
.eq("o.deleted", 0)
.like(StringUtils.isNotBlank(queryDTO.getOrderNo()), "o.order_no", queryDTO.getOrderNo())
.eq(queryDTO.getOrderStatus() != null, "o.order_status", queryDTO.getOrderStatus())
.between(queryDTO.getStartTime() != null && queryDTO.getEndTime() != null,
"o.create_time", queryDTO.getStartTime(), queryDTO.getEndTime())
.orderByDesc("o.create_time");
// 执行分页查询
IPage<Order> orderPage = orderMapper.selectPage(page, wrapper);
// 转换为VO对象
return orderPage.convert(order -> {
OrderVO vo = new OrderVO();
BeanUtils.copyProperties(order, vo);
// 设置关联信息
vo.setUsername(order.getExt().get("username"));
vo.setProductName(order.getExt().get("product_name"));
return vo;
});
}
分组统计查询
public IPage<OrderStatsVO> getOrderStatsByDate(OrderStatsQuery query) {
Page<OrderStatsVO> page = new Page<>(query.getPageNum(), query.getPageSize());
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.select("DATE(create_time) as order_date",
"COUNT(*) as order_count",
"SUM(order_amount) as total_amount",
"AVG(order_amount) as avg_amount")
.groupBy("DATE(create_time)")
.orderByDesc("order_date");
// 使用selectMapsPage获取统计结果
IPage<Map<String, Object>> statsPage = orderMapper.selectMapsPage(page, wrapper);
// 转换为VO对象
return statsPage.convert(map -> {
OrderStatsVO vo = new OrderStatsVO();
vo.setOrderDate((Date) map.get("order_date"));
vo.setOrderCount(((Long) map.get("order_count")).intValue());
vo.setTotalAmount(new BigDecimal(map.get("total_amount").toString()));
vo.setAvgAmount(new BigDecimal(map.get("avg_amount").toString()));
return vo;
});
}
最佳实践建议
- 合理设置分页大小:根据业务需求设置合适的分页大小,一般建议10-50条/页
- 避免深分页:对于大数据量的表,避免使用较大的offset值,可以通过其他方式优化
- 使用覆盖索引:确保查询条件能够命中索引,提高查询性能
- 缓存查询结果:对于相对静态的数据,可以考虑使用缓存减少数据库压力
- 监控慢查询:定期监控和分析慢查询日志,优化性能瓶颈
通过MyBatis-Plus强大的分页和条件查询功能,我们可以轻松应对电商系统中各种复杂的订单查询需求,同时保证查询性能和代码的可维护性。
系统性能优化与监控方案
在电商系统的高并发场景下,数据库性能优化和系统监控是保障系统稳定运行的关键。MyBatis-Plus提供了丰富的性能优化特性和监控支持,能够有效提升系统处理能力并实时掌握系统运行状态。
数据库查询性能优化
1. 批处理操作优化
MyBatis-Plus提供了强大的批处理支持,通过SqlHelper.executeBatch方法可以实现高效的批量数据操作:
// 批量插入优化示例
public boolean batchInsertProducts(List<Product> productList) {
return SqlHelper.executeBatch(productMapper.getClass(), log, productList, 1000,
(sqlSession, product) -> {
productMapper.insert(product);
});
}
// 批量更新优化
public boolean batchUpdateProducts(List<Product> productList) {
return SqlHelper.executeBatch(productMapper.getClass(), log, productList, 1000,
(sqlSession, product) -> {
productMapper.updateById(product);
});
}
批处理参数配置建议:
- 批量大小:建议设置为1000-5000条/批次
- 事务控制:合理设置事务边界,避免大事务
- 内存管理:监控JVM内存使用,避免OOM
2. 分页查询优化
MyBatis-Plus的分页插件支持多种数据库方言,并提供count查询优化:
// 分页查询配置
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
paginationInterceptor.setMaxLimit(1000L); // 设置最大单页限制
paginationInterceptor.setOptimizeJoin(true); // 优化联表count查询
paginationInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
// 分页查询使用
public Page<Product> searchProducts(ProductQuery query, Page<Product> page) {
return productMapper.selectPage(page,
Wrappers.<Product>lambdaQuery()
.eq(Product::getCategoryId, query.getCategoryId())
.like(Product::getName, query.getKeyword())
.orderByDesc(Product::getSalesCount)
);
}
SQL性能分析与监控
1. SQL执行监控
通过自定义拦截器实现SQL执行时间监控:
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
public class SqlPerformanceInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);
private static final long SLOW_SQL_THRESHOLD = 1000; // 1秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
String sqlId = mappedStatement.getId();
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long end = System.currentTimeMillis();
long time = end - start;
if (time > SLOW_SQL_THRESHOLD) {
logger.warn("慢SQL警告 - ID: {}, 耗时: {}ms", sqlId, time);
}
// 记录SQL执行统计
Metrics.counter("sql_execution_total", "sql_id", sqlId).increment();
Metrics.timer("sql_execution_time", "sql_id", sqlId).record(time, TimeUnit.MILLISECONDS);
}
}
}
2. 连接池监控
集成Druid连接池监控:
spring:
datasource:
druid:
filters: stat,wall
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
web-stat-filter:
enabled: true
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
缓存策略优化
1. 二级缓存配置
MyBatis-Plus支持多种缓存实现,推荐使用Caffeine作为本地缓存:
@Configuration
public class CacheConfig {
@Bean
public Cache caffeineCache() {
return new CaffeineCache("mybatisCache",
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.build());
}
}
// Mapper层缓存配置
@CacheNamespace(implementation = CaffeineCache.class)
public interface ProductMapper extends BaseMapper<Product> {
@Select("SELECT * FROM product WHERE category_id = #{categoryId}")
@Options(useCache = true)
List<Product> selectByCategory(@Param("categoryId") Long categoryId);
}
2. 查询结果缓存
对于热点数据使用Redis分布式缓存:
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String PRODUCT_CACHE_KEY = "product:";
private static final long CACHE_EXPIRE = 30 * 60; // 30分钟
public Product getProductWithCache(Long productId) {
String cacheKey = PRODUCT_CACHE_KEY + productId;
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product == null) {
product = productMapper.selectById(productId);
if (product != null) {
redisTemplate.opsForValue().set(cacheKey, product, CACHE_EXPIRE, TimeUnit.SECONDS);
}
}
return product;
}
}
系统监控体系
1. 监控指标收集
@Component
public class SystemMetrics {
// 数据库连接池监控
@Scheduled(fixedRate = 60000)
public void monitorDataSource() {
DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
if (dataSource instanceof DruidDataSource) {
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
Metrics.gauge("datasource.active_count", druidDataSource.getActiveCount());
Metrics.gauge("datasource.pooling_count", druidDataSource.getPoolingCount());
}
}
// JVM监控
@Scheduled(fixedRate = 30000)
public void monitorJvm() {
Runtime runtime = Runtime.getRuntime();
long freeMemory = runtime.freeMemory();
long totalMemory = runtime.totalMemory();
long maxMemory = runtime.maxMemory();
Metrics.gauge("jvm.memory.free", freeMemory);
Metrics.gauge("jvm.memory.total", totalMemory);
Metrics.gauge("jvm.memory.max", maxMemory);
Metrics.gauge("jvm.memory.used", totalMemory - freeMemory);
}
}
2. 告警规则配置
基于Prometheus和Grafana构建监控告警体系:
# prometheus告警规则
groups:
- name: database.rules
rules:
- alert: HighDBActiveConnections
expr: datasource_active_count > 50
for: 5m
labels:
severity: warning
annotations:
summary: "数据库活跃连接数过高"
description: "当前活跃连接数: {{ $value }}"
- alert: SlowSQLQuery
expr: rate(sql_execution_time_sum{sql_id=~".+"}[5m]) > 1000
for: 2m
labels:
severity: critical
annotations:
summary: "慢SQL查询检测"
description: "SQL ID: {{ $labels.sql_id }}, 平均耗时: {{ $value }}ms"
性能优化最佳实践
1. 索引优化策略
-- 商品表索引优化
CREATE INDEX idx_product_category ON product(category_id, status);
CREATE INDEX idx_product_search ON product(name, price, sales_count);
CREATE INDEX idx_product_update ON product(update_time);
-- 订单表索引优化
CREATE INDEX idx_order_user ON order_info(user_id, order_status);
CREATE INDEX idx_order_create ON order_info(create_time);
CREATE INDEX idx_order_payment ON order_info(payment_status, payment_time);
2. 数据库配置优化
# MySQL性能优化配置
[mysqld]
innodb_buffer_pool_size = 16G
innodb_log_file_size = 2G
innodb_flush_log_at_trx_commit = 2
max_connections = 1000
query_cache_size = 0
thread_cache_size = 100
3. 应用层优化
// 使用Lambda表达式避免N+1查询问题
public List<ProductDTO> getProductsWithCategory() {
return productMapper.selectList(
Wrappers.<Product>lambdaQuery()
.select(Product::getId, Product::getName, Product::getPrice,
Product::getCategoryId)
.eq(Product::getStatus, 1)
).stream().map(product -> {
ProductDTO dto = new ProductDTO();
dto.setId(product.getId());
dto.setName(product.getName());
dto.setPrice(product.getPrice());
// 批量查询分类信息,避免N+1
dto.setCategoryName(categoryMap.get(product.getCategoryId()));
return dto;
}).collect(Collectors.toList());
}
监控仪表板设计
通过Grafana构建完整的监控视图:
主要监控面板包括:
- 数据库监控:连接数、QPS、慢查询、锁等待
- JVM监控:内存使用、GC频率、线程状态
- 应用监控:接口响应时间、错误率、吞吐量
- 业务监控:订单量、支付成功率、用户活跃度
总结
通过MyBatis-Plus的性能优化特性和完善的监控体系,电商系统可以实现:
- 数据库操作效率提升:批处理减少网络IO,分页优化降低内存消耗
- 查询性能优化:合理的索引策略和缓存机制大幅提升响应速度
- 系统稳定性保障:实时监控和告警机制及时发现并处理问题
- 资源利用率优化:连接池管理和JVM调优提升系统承载能力
这套性能优化与监控方案能够有效支撑电商系统在高并发场景下的稳定运行,为业务增长提供可靠的技术保障。
总结
通过本实战案例,我们全面掌握了MyBatis-Plus在电商系统中的应用。从数据库设计到业务实现,从基础CRUD到复杂查询优化,MyBatis-Plus提供了完整的解决方案。其强大的注解功能、灵活的条件构造器、高效的分页支持和性能优化特性,使得开发电商系统变得更加高效和可靠。结合合理的监控体系,能够确保系统在高并发场景下的稳定运行,为业务发展提供坚实的技术基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



