程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • 后端开发

    • Spring Boot多模块项目开发
    • Spring Boot图片资源返回
    • Spring Boot文件上传
    • Spring Boot文件下载
    • 对接第三方文件上传
    • Servlet 原生API
    • HttpServletResponse 和 ResponseEntity
    • 后端解决跨域问题
    • 后端拦截器
    • SpringBoot+Vue实现邮件发送与验证码验证
    • 谷歌验证码
    • 利用hutool工具类实现图片验证码
    • 统一返回格式
    • 通用 普通 登录模块
    • 通用 JWT 登录认证模块
    • 通用 普通 注册模块
    • 基于 MyBatis curd
    • 基于 MyBatis-Plus curd
    • Java 常见对象模型
    • 开发枚举的使用
    • MyBatis与MyBatis-Plus日期类型处理
      • 1. 概述
      • 2. 实体类中的日期类型定义
        • 2.1 常用日期类型介绍
        • 2.2 日期类型选择建议
      • 3. MyBatis与MyBatis-Plus日期类型映射
        • 3.1 MyBatis日期类型映射
        • 3.2 MyBatis-Plus日期类型映射
      • 4. 前后端日期交互处理
        • 4.1 前端向后端传递日期
        • 4.1.1 前端日期格式
        • 4.1.2 后端接收前端日期
        • 4.1.3 日期传输DTO定义
        • 4.2 后端向前端返回日期
        • 4.2.1 配置日期返回格式
        • 4.2.2 全局日期格式配置
      • 5. 日期字段自动填充
        • 5.1 MyBatis日期自动填充
        • 5.1.1 使用拦截器实现
        • 5.1.2 在XML中设置默认值
        • 5.2 MyBatis-Plus日期自动填充
      • 6. 日期类型查询操作
        • 6.1 MyBatis日期查询
        • 6.2 MyBatis-Plus日期查询
        • 6.2.1 基本日期查询
        • 6.2.2 高级日期查询
        • 6.2.3 使用LambdaQueryWrapper
      • 7. 日期类型处理常见问题与解决方案
        • 7.1 时区问题
        • 7.2 序列化与反序列化问题
        • 7.3 日期格式转换问题
    • 接口日志拦截基础版
    • 接口日志拦截进阶版
    • 文件操作工具类
    • Spring Boot 数据校验
    • 幂等性
  • 前端开发

  • 开发笔记
  • 后端开发
scholar
2025-03-16
目录

MyBatis与MyBatis-Plus日期类型处理

# MyBatis与MyBatis-Plus日期类型处理

# 1. 概述

日期类型是企业应用开发中最常见且容易出错的数据类型之一。本文全面介绍MyBatis和MyBatis-Plus中日期类型的定义、映射、前后端交互处理以及查询操作,帮助开发者高效处理各种日期相关场景。

# 2. 实体类中的日期类型定义

# 2.1 常用日期类型介绍

/**
 * 用户实体类 - 日期类型示例
 * MyBatis和MyBatis-Plus通用
 */
@Data
@TableName("sys_user") // MyBatis-Plus注解,普通MyBatis不需要
public class User {
    // MyBatis-Plus特有注解,普通MyBatis不需要
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    // Java 8日期类型(推荐)- 两种框架都适用
    private LocalDateTime createTime;  // 日期+时间
    private LocalDate birthday;        // 仅日期
    private LocalTime loginTime;       // 仅时间
    
    // 传统日期类型 - 两种框架都适用
    private Date updateTime;           // java.util.Date
    private java.sql.Date hireDate;    // SQL日期
    private Timestamp lastLoginTime;   // 时间戳
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2.2 日期类型选择建议

Java类型 数据库类型 适用场景 优缺点
LocalDateTime DATETIME/TIMESTAMP 需要日期+时间 推荐,API友好,线程安全
LocalDate DATE 仅需要日期 推荐,不含时间部分
LocalTime TIME 仅需要时间 推荐,不含日期部分
Date DATETIME/TIMESTAMP 兼容旧系统 不推荐,API设计较差
Timestamp TIMESTAMP 需要精确到纳秒 不推荐,除非特殊需求

# 3. MyBatis与MyBatis-Plus日期类型映射

# 3.1 MyBatis日期类型映射

在普通MyBatis中,日期类型映射需要在XML配置文件中设置:

<!-- MyBatis映射文件 - 日期类型映射 -->
<resultMap id="userResultMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 日期类型映射 -->
    <result property="createTime" column="create_time"/>
    <result property="birthday" column="birthday"/>
    <result property="loginTime" column="login_time"/>
    <result property="updateTime" column="update_time"/>
    <result property="hireDate" column="hire_date"/>
    <result property="lastLoginTime" column="last_login_time"/>
</resultMap>

<!-- MyBatis查询示例 -->
<select id="getUserById" resultMap="userResultMap">
    SELECT * FROM sys_user WHERE id = #{id}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

MyBatis自定义类型处理器

对于特殊的日期格式处理,可以自定义TypeHandler:

/**
 * MyBatis自定义日期类型处理器
 */
@MappedTypes(LocalDateTime.class)
@MappedJdbcTypes(JdbcType.TIMESTAMP)
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) 
            throws SQLException {
        ps.setTimestamp(i, Timestamp.valueOf(parameter));
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnName);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnIndex);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }

    @Override
    public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp timestamp = cs.getTimestamp(columnIndex);
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

然后在MyBatis配置文件中注册:

<!-- mybatis-config.xml -->
<configuration>
    <typeHandlers>
        <typeHandler handler="com.example.handler.LocalDateTimeTypeHandler"/>
        <!-- 可以添加其他日期类型处理器 -->
    </typeHandlers>
</configuration>
1
2
3
4
5
6
7

# 3.2 MyBatis-Plus日期类型映射

MyBatis-Plus会自动处理常见的日期类型映射,无需额外配置:

/**
 * MyBatis-Plus实体类
 * 自动处理日期映射
 */
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    // 无需额外配置,自动映射
    private LocalDateTime createTime;
    private LocalDate birthday;
    
    // 自动填充示例
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

MyBatis-Plus自定义类型处理器

与MyBatis相同,对于特殊场景也可以自定义TypeHandler,并在MybatisPlusConfig中注册:

/**
 * MyBatis-Plus配置类
 */
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件等
        return interceptor;
    }
    
    /**
     * 注册自定义TypeHandler
     */
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            // 注册TypeHandler
            configuration.getTypeHandlerRegistry().register(LocalDateTimeTypeHandler.class);
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 4. 前后端日期交互处理

# 4.1 前端向后端传递日期

# 4.1.1 前端日期格式

// 前端JavaScript日期格式示例 - 适用于MyBatis和MyBatis-Plus

// 1. ISO 8601标准格式(推荐)
const isoDateString = "2023-05-15T14:30:00Z"; // 带时区信息
const isoLocalDateString = "2023-05-15T14:30:00"; // 本地时间

// 2. 日期部分
const dateOnlyString = "2023-05-15";

// 3. 自定义格式
const customDateString = "2023-05-15 14:30:00";

// 4. 时间戳(毫秒)
const timestamp = 1684157400000;

// 使用axios发送日期数据示例
axios.post('/api/user', {
  username: 'zhangsan',
  birthday: dateOnlyString,         // 日期字符串
  createTime: customDateString,     // 日期时间字符串
  loginTimestamp: timestamp         // 时间戳
});

// 使用URL参数传递日期
axios.get(`/api/users?startDate=${encodeURIComponent(dateOnlyString)}&endDate=${encodeURIComponent(customDateString)}`);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 4.1.2 后端接收前端日期

/**
 * 后端接收前端日期参数 - 控制器示例
 * MyBatis和MyBatis-Plus通用
 */
@RestController
@RequestMapping("/api")
public class UserController {
    @Autowired
    private UserService userService;
    
    /**
     * 接收请求体中的日期(POST/PUT请求)
     */
    @PostMapping("/user")
    public Result createUser(@RequestBody UserDTO userDTO) {
        // UserDTO中定义了日期字段和注解
        return Result.success(userService.createUser(userDTO));
    }
    
    /**
     * 接收URL参数中的日期(GET请求)
     */
    @GetMapping("/users")
    public Result getUsersByDateRange(
            @RequestParam("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        return Result.success(userService.getUsersByDateRange(startDate, endDate));
    }
    
    /**
     * 接收URL参数中的日期时间(GET请求)
     */
    @GetMapping("/logs")
    public Result getLogsByTimeRange(
            @RequestParam("startTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
            @RequestParam("endTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
        return Result.success(userService.getLogsByTimeRange(startTime, endTime));
    }
    
    /**
     * 接收时间戳参数(毫秒)
     */
    @GetMapping("/events")
    public Result getEventsByTimestamp(@RequestParam("timestamp") Long timestamp) {
        // 转换时间戳为LocalDateTime
        LocalDateTime dateTime = LocalDateTime.ofInstant(
                Instant.ofEpochMilli(timestamp), 
                ZoneId.systemDefault()
        );
        return Result.success(userService.getEventsByTime(dateTime));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 4.1.3 日期传输DTO定义

/**
 * 用户数据传输对象 - 包含日期字段和注解
 * MyBatis和MyBatis-Plus通用
 */
@Data
public class UserDTO {
    private Long id;
    private String username;
    
    /**
     * 仅日期字段
     * 前端传入格式: "2023-05-15"
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")  // 用于解析前端传入的日期
    @JsonFormat(pattern = "yyyy-MM-dd")      // 用于格式化返回给前端的日期
    private LocalDate birthday;
    
    /**
     * 日期时间字段
     * 前端传入格式: "2023-05-15 14:30:00"
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    
    /**
     * ISO格式日期时间
     * 前端传入格式: "2023-05-15T14:30:00"
     */
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDateTime updateTime;
    
    /**
     * 时间戳字段
     * 前端传入时间戳(毫秒): 1684157400000
     */
    private Long loginTimestamp;
    
    /**
     * 获取loginTimestamp转换后的LocalDateTime
     */
    public LocalDateTime getLoginTime() {
        if (loginTimestamp == null) {
            return null;
        }
        return LocalDateTime.ofInstant(
                Instant.ofEpochMilli(loginTimestamp),
                ZoneId.systemDefault()
        );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 4.2 后端向前端返回日期

当后端使用@JsonFormat注解或全局配置将日期格式化为标准字符串格式后,前端在大多数情况下可以直接使用这些日期字符串,不需要进行额外的解析操作。这是因为:

  1. 直接显示场景:如果只是在界面上显示日期,前端可以直接使用格式化后的字符串,因为它已经是人类可读的格式(如"2023-05-15"或"2023-05-15 14:30:00")。
  2. 基本交互:对于表单回显、数据展示等基本场景,格式化后的日期字符串可以直接使用。

# 4.2.1 配置日期返回格式

/**
 * 用户返回对象 - 返回给前端的日期格式配置
 * MyBatis和MyBatis-Plus通用
 */
@Data
public class UserVO {
    private Long id;
    private String username;
    
    /**
     * 标准日期格式(年月日)
     * 返回格式: "2023-05-15"
     */
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate birthday;
    
    /**
     * 标准日期时间格式
     * 返回格式: "2023-05-15 14:30:00"
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    
    /**
     * ISO 8601格式
     * 返回格式: "2023-05-15T14:30:00"
     */
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    
    /**
     * 返回时间戳(毫秒)
     * 如果前端需要时间戳格式
     */
    private Long timestamp;
    
    /**
     * 自定义格式的日期
     * 返回格式: "2023年05月15日"
     */
    @JsonFormat(pattern = "yyyy年MM月dd日", timezone = "GMT+8")
    private LocalDate specialDate;
    
    /**
     * 仅时间部分
     * 返回格式: "14:30:00"
     */
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime loginTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 4.2.2 全局日期格式配置

# application.yml - 全局日期格式配置
# MyBatis和MyBatis-Plus通用
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    serialization:
      write-dates-as-timestamps: false  # 禁止将日期序列化为时间戳
1
2
3
4
5
6
7
8
/**
 * 自定义Jackson配置类(更灵活的配置)
 * MyBatis和MyBatis-Plus通用
 */
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 注册Java 8日期时间模块
        objectMapper.registerModule(new JavaTimeModule());
        
        // 禁止将日期序列化为时间戳
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // 配置日期格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(dateFormat);
        
        // 配置时区
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        
        return objectMapper;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 5. 日期字段自动填充

# 5.1 MyBatis日期自动填充

在普通MyBatis中,没有内置的自动填充功能,需要手动实现:

# 5.1.1 使用拦截器实现

/**
 * MyBatis自动填充拦截器
 */
@Component
@Intercepts({
    @Signature(type = Executor.class, method = "update", 
        args = {MappedStatement.class, Object.class})
})
public class DateFillInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        if (args[1] != null) {
            Object parameter = args[1];
            // 填充日期字段
            fillDateFields(parameter);
        }
        return invocation.proceed();
    }

    private void fillDateFields(Object parameter) {
        // 反射获取对象属性
        Class<?> clazz = parameter.getClass();
        try {
            // 处理创建时间
            handleField(clazz, parameter, "createTime", false);
            // 处理更新时间
            handleField(clazz, parameter, "updateTime", true);
        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
        }
    }

    private void handleField(Class<?> clazz, Object parameter, String fieldName, boolean alwaysFill) {
        try {
            Field field = getField(clazz, fieldName);
            if (field != null) {
                field.setAccessible(true);
                Object value = field.get(parameter);
                // 如果字段为空或者总是填充
                if (value == null || alwaysFill) {
                    if (field.getType() == LocalDateTime.class) {
                        field.set(parameter, LocalDateTime.now());
                    } else if (field.getType() == Date.class) {
                        field.set(parameter, new Date());
                    }
                }
            }
        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
        }
    }

    private Field getField(Class<?> clazz, String fieldName) {
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            // 字段不存在
            return null;
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以设置属性
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

然后在MyBatis配置中注册:

@Configuration
public class MyBatisConfig {
    
    @Bean
    public DateFillInterceptor dateFillInterceptor() {
        return new DateFillInterceptor();
    }
    
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            configuration.addInterceptor(dateFillInterceptor());
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5.1.2 在XML中设置默认值

<!-- MyBatis插入语句 -->
<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO sys_user (
        username, 
        birthday,
        create_time,  
        update_time
    ) VALUES (
        #{username}, 
        #{birthday}, 
        <!-- 使用数据库函数设置默认值 -->
        #{createTime,jdbcType=TIMESTAMP},
        #{updateTime,jdbcType=TIMESTAMP}
    )
    <!-- 如果参数为空,使用数据库函数 -->
    <selectKey keyProperty="id" resultType="java.lang.Long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

<!-- 更新语句 -->
<update id="updateUser" parameterType="com.example.entity.User">
    UPDATE sys_user
    SET username = #{username},
        birthday = #{birthday},
        update_time = NOW()  <!-- 始终更新 -->
    WHERE id = #{id}
</update>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5.2 MyBatis-Plus日期自动填充

MyBatis-Plus提供了内置的自动填充功能:

/**
 * 自定义元对象处理器
 * 实现创建时间、更新时间自动填充
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    private static final Logger log = LoggerFactory.getLogger(MyMetaObjectHandler.class);

    /**
     * 插入操作自动填充
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始执行插入填充");
        
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        
        // 创建时间字段自动填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, now);
        
        // 更新时间字段自动填充
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, now);
        
        // 如果使用Date类型
        Date utilNow = new Date();
        this.strictInsertFill(metaObject, "createDate", Date.class, utilNow);
        this.strictInsertFill(metaObject, "updateDate", Date.class, utilNow);
    }

    /**
     * 更新操作自动填充
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始执行更新填充");
        
        // 仅更新"更新时间"字段
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        
        // 如果使用Date类型
        this.strictUpdateFill(metaObject, "updateDate", Date.class, new Date());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

实体类中应用自动填充:

/**
 * 用户实体类 - 应用自动填充
 */
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String username;
    
    /**
     * 创建时间 - 插入时自动填充
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    /**
     * 更新时间 - 插入和更新时都自动填充
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6. 日期类型查询操作

# 6.1 MyBatis日期查询

在原生MyBatis中,日期查询通常在XML中定义:

<!-- MyBatis XML映射文件 -->
<mapper namespace="com.example.mapper.UserMapper">

    <!-- 精确日期查询 -->
    <select id="getUsersByExactDate" resultMap="userResultMap">
        SELECT * FROM sys_user WHERE birthday = #{date}
    </select>
    
    <!-- 日期范围查询 -->
    <select id="getUsersByDateRange" resultMap="userResultMap">
        SELECT * FROM sys_user 
        WHERE create_time BETWEEN #{startTime} AND #{endTime}
    </select>
    
    <!-- 日期条件查询 -->
    <select id="getUsersWithDateCondition" resultMap="userResultMap">
        SELECT * FROM sys_user
        <where>
            <if test="startDate != null">
                AND create_time >= #{startDate}
            </if>
            <if test="endDate != null">
                AND create_time &lt;= #{endDate}
            </if>
            <if test="birthday != null">
                AND birthday = #{birthday}
            </if>
        </where>
    </select>
    
    <!-- 日期函数查询 -->
    <select id="getUsersByMonthAndYear" resultMap="userResultMap">
        SELECT * FROM sys_user
        WHERE MONTH(birthday) = #{month} AND YEAR(birthday) = #{year}
    </select>
    
    <!-- 最近N天用户查询 -->
    <select id="getRecentUsers" resultMap="userResultMap">
        SELECT * FROM sys_user
        WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY)
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

对应的Mapper接口:

/**
 * MyBatis Mapper接口
 */
public interface UserMapper {
    List<User> getUsersByExactDate(LocalDate date);
    
    List<User> getUsersByDateRange(LocalDateTime startTime, LocalDateTime endTime);
    
    List<User> getUsersWithDateCondition(Map<String, Object> params);
    
    List<User> getUsersByMonthAndYear(int month, int year);
    
    List<User> getRecentUsers(int days);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

调用示例:

/**
 * 使用MyBatis进行日期查询
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> findByDateRange(LocalDateTime start, LocalDateTime end) {
        return userMapper.getUsersByDateRange(start, end);
    }
    
    @Override
    public List<User> findWithDateCondition(LocalDate startDate, LocalDate endDate, LocalDate birthday) {
        Map<String, Object> params = new HashMap<>();
        if (startDate != null) {
            params.put("startDate", startDate.atStartOfDay());
        }
        if (endDate != null) {
            params.put("endDate", endDate.atTime(23, 59, 59));
        }
        params.put("birthday", birthday);
        return userMapper.getUsersWithDateCondition(params);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 6.2 MyBatis-Plus日期查询

MyBatis-Plus提供了更简洁的查询方式:

# 6.2.1 基本日期查询

/**
 * 基本日期查询示例 - MyBatis-Plus
 */
@Service
public class UserQueryService {
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 精确日期查询
     */
    public List<User> getUsersByExactDate() {
        // 创建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 精确匹配日期
        LocalDate date = LocalDate.of(2023, 5, 15);
        queryWrapper.eq("birthday", date);
        
        // 精确匹配日期时间
        LocalDateTime dateTime = LocalDateTime.of(2023, 5, 15, 14, 30, 0);
        queryWrapper.eq("create_time", dateTime);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 日期范围查询
     */
    public List<User> getUsersByDateRange() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 日期范围查询
        LocalDate startDate = LocalDate.of(2023, 1, 1);
        LocalDate endDate = LocalDate.of(2023, 12, 31);
        queryWrapper.between("birthday", startDate, endDate);
        
        // 日期时间范围查询
        LocalDateTime startTime = LocalDateTime.of(2023, 5, 1, 0, 0, 0);
        LocalDateTime endTime = LocalDateTime.of(2023, 5, 31, 23, 59, 59);
        queryWrapper.between("create_time", startTime, endTime);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 日期比较查询
     */
    public List<User> getUsersByDateComparison() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 大于某个日期
        LocalDate date = LocalDate.of(2000, 1, 1);
        queryWrapper.gt("birthday", date);
        
        // 小于某个日期时间
        LocalDateTime dateTime = LocalDateTime.now();
        queryWrapper.lt("create_time", dateTime);
        
        return userMapper.selectList(queryWrapper);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 6.2.2 高级日期查询

/**
 * 高级日期查询示例 - MyBatis-Plus
 */
@Service
public class AdvancedDateQueryService {
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 查询指定日期(忽略时间部分)
     */
    public List<User> getUsersByDateOnly() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 方法1:使用数据库DATE函数
        LocalDate targetDate = LocalDate.of(2023, 5, 15);
        queryWrapper.apply("DATE(create_time) = {0}", targetDate);
        
        // 方法2:使用日期范围限定
        LocalDateTime startOfDay = LocalDate.of(2023, 5, 15).atStartOfDay();
        LocalDateTime endOfDay = LocalDate.of(2023, 5, 15).atTime(23, 59, 59, 999999999);
        queryWrapper.between("create_time", startOfDay, endOfDay);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 查询最近N天的数据
     */
    public List<User> getRecentUsers(int days) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 方法1:使用Java计算日期
        LocalDateTime daysAgo = LocalDateTime.now().minusDays(days);
        queryWrapper.ge("create_time", daysAgo);
        
        // 方法2:使用数据库函数
        queryWrapper.apply("create_time >= DATE_SUB(NOW(), INTERVAL {0} DAY)", days);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 按月份统计用户
     */
    public List<Map<String, Object>> countUsersByMonth() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 按月份分组统计
        queryWrapper.select("DATE_FORMAT(create_time, '%Y-%m') as month, COUNT(*) as count")
                   .groupBy("DATE_FORMAT(create_time, '%Y-%m')")
                   .orderByAsc("month");
        
        return userMapper.selectMaps(queryWrapper);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 6.2.3 使用LambdaQueryWrapper

/**
 * 使用LambdaQueryWrapper进行日期查询示例 - MyBatis-Plus特有
 */
@Service
public class LambdaDateQueryService {
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 基本日期查询(类型安全)
     */
    public List<User> getUsersByBirthday(LocalDate birthday) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        
        // 等值查询
        queryWrapper.eq(User::getBirthday, birthday);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 日期范围查询(类型安全)
     */
    public List<User> getUsersInDateRange(LocalDateTime startTime, LocalDateTime endTime) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        
        // 范围查询
        queryWrapper.between(User::getCreateTime, startTime, endTime);
        
        return userMapper.selectList(queryWrapper);
    }
    
    /**
     * 动态条件查询(类型安全)
     */
    public List<User> getDynamicDateQuery(UserDateQuery query) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        
        // 仅当参数非空时添加条件
        queryWrapper.eq(query.getBirthday() != null, User::getBirthday, query.getBirthday())
                   .ge(query.getStartTime() != null, User::getCreateTime, query.getStartTime())
                   .le(query.getEndTime() != null, User::getCreateTime, query.getEndTime());
        
        // 按创建时间降序排序
        queryWrapper.orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(queryWrapper);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 7. 日期类型处理常见问题与解决方案

# 7.1 时区问题

/**
 * 时区问题处理示例
 */
public class TimeZoneSolutions {
    /**
     * 问题:不同时区日期处理
     * 在国际化应用中,用户可能位于不同时区
     */
    
    /**
     * 解决方案1:在JSON序列化时指定时区
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    
    /**
     * 解决方案2:使用ZonedDateTime明确处理时区
     */
    public void handleTimeZones() {
        // 系统默认时区的当前时间
        ZonedDateTime localZoned = ZonedDateTime.now();
        
        // 特定时区的时间
        ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
        ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
        
        // 时区转换
        ZonedDateTime convertedTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
        
        // 转换为LocalDateTime(不含时区信息)
        LocalDateTime localDateTime = convertedTime.toLocalDateTime();
        
        System.out.println("上海时间: " + shanghaiTime);
        System.out.println("纽约时间: " + newYorkTime);
        System.out.println("转换后时间: " + convertedTime);
        System.out.println("本地时间: " + localDateTime);
    }
    
    /**
     * 解决方案3:使用UTC作为存储标准
     * 
     * 1. 在数据库中始终存储UTC时间
     * 2. 显示时根据用户时区进行转换
     */
    public LocalDateTime convertToUTC(LocalDateTime localDateTime, String zoneId) {
        ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(zoneId));
        ZonedDateTime utcDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC"));
        return utcDateTime.toLocalDateTime();
    }
    
    public LocalDateTime convertFromUTC(LocalDateTime utcDateTime, String zoneId) {
        ZonedDateTime utcZoned = utcDateTime.atZone(ZoneId.of("UTC"));
        ZonedDateTime localZoned = utcZoned.withZoneSameInstant(ZoneId.of(zoneId));
        return localZoned.toLocalDateTime();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 7.2 序列化与反序列化问题

/**
 * 日期序列化问题解决方案
 */
public class DateSerializationSolutions {
    /**
     * 问题:前后端日期格式不一致
     */
    
    /**
     * 解决方案1:实体类使用@JsonFormat注解
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    
    /**
     * 解决方案2:全局Jackson配置类
     */
    @Configuration
    public static class JacksonConfig {
        @Bean
        public ObjectMapper objectMapper() {
            ObjectMapper objectMapper = new ObjectMapper();
            
            // 注册Java 8日期模块
            objectMapper.registerModule(new JavaTimeModule());
            
            // 禁用将日期序列化为时间戳
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            
            // 配置日期格式
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            objectMapper.setDateFormat(dateFormat);
            
            // 设置时区
            objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
            
            return objectMapper;
        }
    }
    
    /**
     * 解决方案3:自定义日期序列化器
     */
    public static class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value != null) {
                gen.writeString(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }
        }
    }
    
    /**
     * 解决方案4:自定义日期反序列化器
     */
    public static class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            String dateString = p.getText();
            if (StringUtils.isNotEmpty(dateString)) {
                try {
                    return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                } catch (Exception e) {
                    // 尝试其他格式
                    try {
                        return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                    } catch (Exception ex) {
                        throw new RuntimeException("无法解析日期: " + dateString);
                    }
                }
            }
            return null;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 7.3 日期格式转换问题

/**
 * 日期格式转换解决方案
 */
public class DateFormatSolutions {
    /**
     * 问题:需要在不同日期格式之间转换
     */
    
    /**
     * 解决方案:使用DateTimeFormatter进行格式转换
     */
    public void dateFormatConversion() {
        // 当前日期时间
        LocalDateTime now = LocalDateTime.now();
        
        // 定义不同格式
        DateTimeFormatter standardFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
        
        // 格式化输出
        String standardFormat = now.format(standardFormatter);  // 2023-05-15 14:30:00
        String isoFormat = now.format(isoFormatter);            // 2023-05-15T14:30:00
        String customFormat = now.format(customFormatter);      // 2023年05月15日 14时30分00秒
        
        // 解析不同格式日期
        LocalDateTime dateTime1 = LocalDateTime.parse("2023-05-15 14:30:00", standardFormatter);
        LocalDateTime dateTime2 = LocalDateTime.parse("2023-05-15T14:30:00", isoFormatter);
        LocalDateTime dateTime3 = LocalDateTime.parse("2023年05月15日 14时30分00秒", customFormatter);
        
        System.out.println("标准格式: " + standardFormat);
        System.out.println("ISO格式: " + isoFormat);
        System.out.println("自定义格式: " + customFormat);
    }
    
    /**
     * 处理多种可能的日期格式
     */
    public LocalDateTime parseMultiFormatDate(String dateString) {
        // 定义可能的日期格式
        List<DateTimeFormatter> formatters = Arrays.asList(
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"),
            DateTimeFormatter.ISO_LOCAL_DATE_TIME,
            DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒")
        );
        
        // 尝试每种格式解析
        for (DateTimeFormatter formatter : formatters) {
            try {
                return LocalDateTime.parse(dateString, formatter);
            } catch (Exception e) {
                // 当前格式不匹配,尝试下一个
                continue;
            }
        }
        
        // 所有格式都不匹配
        throw new IllegalArgumentException("无法解析日期: " + dateString);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
编辑此页 (opens new window)
上次更新: 2025/03/16, 22:19:39
开发枚举的使用
接口日志拦截基础版

← 开发枚举的使用 接口日志拦截基础版→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式