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
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
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
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
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
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
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
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
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
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注解或全局配置将日期格式化为标准字符串格式后,前端在大多数情况下可以直接使用这些日期字符串,不需要进行额外的解析操作。这是因为:
- 直接显示场景:如果只是在界面上显示日期,前端可以直接使用格式化后的字符串,因为它已经是人类可读的格式(如"2023-05-15"或"2023-05-15 14:30:00")。
- 基本交互:对于表单回显、数据展示等基本场景,格式化后的日期字符串可以直接使用。
# 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
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
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
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
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
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
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
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
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 <= #{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
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
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
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
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
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
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
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
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
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