关于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 &lt;= #{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:全表扫描(效率最低,需优化)。
posted @ 2025-08-19 16:20  难忘是想起  阅读(0)  评论(0)    收藏  举报  来源