关于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;
}
// 解析为 YearMonth
YearMonth yearMonth = YearMonth.parse(yearMonthStr, YEAR_MONTH_FORMATTER);
LocalDateTime startLdt = yearMonth.atDay(1).atStartOfDay(); // 2024-06-01T00:00
LocalDateTime endLdt = yearMonth.atEndOfMonth().atTime(LocalTime.MAX); // 2024-06-30T23:59:59.999
Date 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;
}
// 解析为 LocalDate
LocalDate localDate = LocalDate.parse(dayStr, YEAR_MONTH_DAY_FORMATTER);
LocalDateTime startLdt = localDate.atStartOfDay(); // 2024-06-15T00:00
LocalDateTime endLdt = localDate.atTime(LocalTime.MAX); // 2024-06-15T23:59:59.999
Date 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写法(多表关联)
@Override
public 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写法(单表查询)
@Override
public 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
*
FROM
test1 t1
INNER JOIN test2 t2 ON t1.id = t2.id
WHERE 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
*
FROM
test t1
WHERE
t1.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
*
FROM
test t1
WHERE
DATE_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:全表扫描(效率最低,需优化)。


浙公网安备 33010602011771号