关于mybatis表关联查询和mybatis-Plus单表查询传入时间查询数据(走索引)
mysql 8.0字段数据类型为datetime
-- 为create_time字段创建普通索引
CREATE INDEX idx_test_create_time ON test (create_time);
-- 为create_time字段类型为datetime`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
时间转换工具类
package com.boboboom.pro.api.utils;import io.micrometer.common.util.StringUtils;import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;public class DateRangeUtils {// region ====== 年月格式:yyyy-MM ======private static final String YEAR_MONTH_PATTERN = "yyyy-MM";private static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern(YEAR_MONTH_PATTERN);/*** 根据 yyyy-MM 格式字符串(如 "2024-06"),返回该月第一天 00:00:00 和最后一天 23:59:59 的 Date 对象** @param yearMonthStr 如 "2024-06"* @return MonthDateRange 包含 startDate 和 endDate*/public static MonthDateRange getDateRangeByYearMonth(String yearMonthStr) {if (StringUtils.isBlank(yearMonthStr)) {return null;}// 解析为 YearMonthYearMonth yearMonth = YearMonth.parse(yearMonthStr, YEAR_MONTH_FORMATTER);LocalDateTime startLdt = yearMonth.atDay(1).atStartOfDay(); // 2024-06-01T00:00LocalDateTime endLdt = yearMonth.atEndOfMonth().atTime(LocalTime.MAX); // 2024-06-30T23:59:59.999Date startDate = Date.from(startLdt.atZone(ZoneId.systemDefault()).toInstant());Date endDate = Date.from(endLdt.atZone(ZoneId.systemDefault()).toInstant());return new MonthDateRange(startDate, endDate);}// endregion// region ====== 年月日格式:yyyy-MM-dd ======private static final String YEAR_MONTH_DAY_PATTERN = "yyyy-MM-dd";private static final DateTimeFormatter YEAR_MONTH_DAY_FORMATTER = DateTimeFormatter.ofPattern(YEAR_MONTH_DAY_PATTERN);/*** 根据 yyyy-MM-dd 格式字符串(如 "2024-06-15"),返回当天 00:00:00 和 23:59:59 的 Date 对象** @param dayStr 如 "2024-06-15"* @return DayDateRange 包含当天的开始和结束时间*/public static DayDateRange getDateRangeByDay(String dayStr) {if (StringUtils.isBlank(dayStr)) {return null;}// 解析为 LocalDateLocalDate localDate = LocalDate.parse(dayStr, YEAR_MONTH_DAY_FORMATTER);LocalDateTime startLdt = localDate.atStartOfDay(); // 2024-06-15T00:00LocalDateTime endLdt = localDate.atTime(LocalTime.MAX); // 2024-06-15T23:59:59.999Date startDate = Date.from(startLdt.atZone(ZoneId.systemDefault()).toInstant());Date endDate = Date.from(endLdt.atZone(ZoneId.systemDefault()).toInstant());return new DayDateRange(startDate, endDate);}// endregion// region ====== 返回值对象定义 ======/*** 用于封装 年月查询 的开始和结束时间*/public static class MonthDateRange {private final Date startDate;private final Date endDate;public MonthDateRange(Date startDate, Date endDate) {this.startDate = startDate;this.endDate = endDate;}public Date getStartDate() {return startDate;}public Date getEndDate() {return endDate;}}/*** 用于封装 日查询 的开始和结束时间*/public static class DayDateRange {private final Date startDate;private final Date endDate;public DayDateRange(Date startDate, Date endDate) {this.startDate = startDate;this.endDate = endDate;}public Date getStartDate() {return startDate;}public Date getEndDate() {return endDate;}}// endregion
}
实体类中时间入参
@Data
public class VipAccountQuery extends PageQuery {/*** 开始时间*/private String startTime;/*** 结束时间*/private String endTime;/*** 创建时间(不会走索引入参示例)*/private String createTime;}
ServiceImpl
//mybatis写法(多表关联) @Overridepublic Pager<VipAccountDTO> pager(VipAccountQuery pageQuery, OptUserDTO optUserDTO) {Page<VipAccountDTO> page = pageQuery.toPage();Date startDate = null;Date endDate = null;if (StrUtil.isNotBlank(pageQuery.getStartTime())) {DateRangeUtils.DayDateRange startRange = DateRangeUtils.getDateRangeByDay(pageQuery.getStartTime());startDate = startRange != null ? startRange.getStartDate() : null;}if (StrUtil.isNotBlank(pageQuery.getEndTime())) {DateRangeUtils.DayDateRange endRange = DateRangeUtils.getDateRangeByDay(pageQuery.getEndTime());endDate = endRange != null ? endRange.getEndDate() : null;}// 执行分页查询Page<VipAccountDTO> resultPage = vipAccountMapper.selectPage(page, pageQuery, startDate, endDate,createTime);return PagerTool.toPager(resultPage, resultPage.getRecords());}//mybatis-plus写法(单表查询)@Overridepublic VipAccountMonthResult getFleetMonthlyIncomeAndExpense(VipAccountFlowParam pageQuery) {log.info("查询车队当月收支统计:{}", JSON.toJSONString(pageQuery));AssertUtil.notEmpty(pageQuery.getCreateTime(), "查询时间不能为空");AssertUtil.notNull(pageQuery.getVipAccountId(), "会员账户ID不能为空");Date startDate = Objects.requireNonNull(DateRangeUtils.getDateRangeByYearMonth(pageQuery.getCreateTime())).getStartDate();Date endDate = Objects.requireNonNull(DateRangeUtils.getDateRangeByYearMonth(pageQuery.getCreateTime())).getEndDate();Long vipAccountId = pageQuery.getVipAccountId();List<VipAccountFlowEntity> vipAccountFlowEntities = vipAccountFlowMapper.selectList(new LambdaQueryWrapper<>(VipAccountFlowEntity.class).eq(VipAccountFlowEntity::getAccountVipId, vipAccountId).ge(VipAccountFlowEntity::getCreateTime, startDate).lt(VipAccountFlowEntity::getCreateTime, endDate));System.out.println(JSON.toJSONString(vipAccountFlowEntities));// return vipAccountFlowMapper.getFleetMonthlyIncomeAndExpense(startDate, endDate,vipAccountId);}
Mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boboboom.pro.api.system.vipaccount.param.VipAccountParam;
import com.boboboom.pro.module.system.bizaccount.model.dto.VipAccountDTO;
import com.boboboom.pro.module.system.bizaccount.model.entity.VipAccountEntity;
import com.boboboom.pro.module.system.bizaccount.model.param.VipAccountQuery;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.math.BigDecimal;
import java.util.Date;@Mapper
public interface VipAccountMapper extends BaseMapper<VipAccountEntity> {Page<VipAccountDTO> selectPage(Page<VipAccountDTO> page,@Param("pageQuery") VipAccountQuery pageQuery,@Param("startTime") Date startTime,@Param("endTime") Date endTime,@Param("createTime") Date createTime);}
Mapper.xml
<!-- 多表关联查询使用 -->
<select id="selectPage" resultType="com.model.dto.VipAccountDTO">SELECT*FROMtest1 t1INNER JOIN test2 t2 ON t1.id = t2.idWHERE 1=1<if test="startTime != null">AND t1.create_time >= #{startTime}</if><if test="endTime != null">AND t1.create_time <= #{endTime}</if>ORDER BY t1.create_time DESC</select>
下面是不会走索引的查询
<if test="pageQuery.createTime != null">AND DATE_FORMAT(t1.create_time, '%Y-%m') = #{pageQuery.createTime}</if>
最终sql语句(走索引)
SELECT*
FROMtest t1
WHEREt1.create_time >= '2025-08-01 00:00:00.0'AND t1.create_time <= '2025-08-31 23:59:59.999';
PS:如果查询范围过大,全表扫描更高效
MySQL 优化器会判断:如果查询的记录数占表总记录数的比例较高(通常超过 20%-30%),全表扫描(ALL
)可能比走索引更快(因为索引需要额外的磁盘 I/O 和回表操作)。
例如:如果 8 月份的数据占表中大部分记录,优化器可能直接选择全表扫描。
最终sql语句(不走索引)
SELECT*
FROMtest t1
WHEREDATE_FORMAT(t1.create_time, '%Y-%m') = '2025-08'
索引命中级别type
:访问类型(最重要的参数之一)
表示 MySQL 访问表中数据的方式,反映查询效率,从优到差排序:
system
:表中只有一行数据(如系统表),效率最高。const
:通过主键或唯一索引查询,最多返回一行,如WHERE id = 1
。eq_ref
:多表连接时,被连接表通过主键或唯一索引匹配,每行只匹配一次(如JOIN
中ON
条件使用主键)。ref
:非唯一索引的等值查询,可能返回多行(如普通索引的WHERE name = 'xxx'
)。range
:索引范围查询(如>、<、BETWEEN、IN
)。index
:扫描整个索引树(比ALL
好,因为索引通常比数据小)。ALL
:全表扫描(效率最低,需优化)。