SQL子查询与MyBatis映射过程
前言
以下是一个结合 SQL 别名、子查询、MyBatis 字段映射和代码复用的完整案例,以用户管理系统为例:
场景描述:
查询用户信息时需同时显示:
- 基础用户信息
- 所属部门名称
- 最近3个月的订单数量
- 使用代码复用优化重复SQL片段
1. 数据库表结构
用户表(User)

部门表(Department)

订单表(Order)

2. MyBatis Mapper XML
<!-- 结果映射 -->
<mapper namespace="com.example.dao.UserMapper">
<resultMap id="userResultMap" type="实体类的全限定名">
<result property="userId" column="USER_ID"/>
<result property="username" column="USER_NAME"/>
<result property="departmentName" column="DEPARTMENT_NAME"/>
<result property="orderCount" column="ORDER_COUNT"/>
<result property="createTime" column="CREATE_TIME"/>
</resultMap>
</mapper>
<!-- 定义可复用的基础字段 -->
<sql id="baseUserColumns">
u.id AS USER_ID,
u.username AS USER_NAME,
u.create_time AS CREATE_TIME
</sql>
<!-- 定义部门关联字段 -->
<sql id="deptColumns">
d.dept_name AS DEPARTMENT_NAME
</sql>
<!-- 复合查询 -->
<select id="selectUserWithStats" resultMap="userResultMap">
SELECT
<!-- 复用基础字段 -->
<include refid="baseUserColumns"/>,
<!-- 复用部门字段 -->
<include refid="deptColumns"/>,
<!-- 使用子查询和别名 -->
(
SELECT COUNT(*)
FROM `order` o
WHERE o.user_id = u.id
AND o.order_time >= DATE_SUB(NOW(), INTERVAL 3 MONTH)
) AS ORDER_COUNT
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
WHERE u.id = #{userId}
</select>3. Java 实体类
public class UserDTO {
private Long userId; // 映射 USER_ID
private String username; // 映射 USER_NAME
private String departmentName; // 映射 DEPARTMENT_NAME
private Integer orderCount; // 映射 ORDER_COUNT
private Date createTime; // 映射 CREATE_TIME
// Getters and Setters
}
4. 技术点解析
子查询封装
(SELECT COUNT(*) ...) AS ORDER_COUNT
- 将复杂统计逻辑封装在子查询中:这种方法可以使得主查询更加简洁,同时将复杂的业务逻辑(例如统计订单数量)隔离在一个子查询内,便于维护。
- 避免多次查询数据库:通过一次性获取所有所需的数据(包括通过子查询计算得到的数据),减少了与数据库交互的次数,提高了性能。
MyBatis字段映射
<resultMap id="userResultMap" type="UserDTO"> <id property="userId" column="USER_ID"/> <result property="departmentName" column="DEPARTMENT_NAME"/> </resultMap>
- 实现数据库字段到对象属性的映射:
resultMap用于定义如何将数据库查询结果映射到Java对象的属性上。 - 处理命名差异问题:通过明确指定
column和property,解决了数据库字段命名与Java属性命名不一致的问题,同时也考虑了大小写的差异。
SQL代码复用
<include refid="baseUserColumns"/> <include refid="deptColumns"/>
- 统一管理公共字段:使用
<sql>标签定义可复用的SQL片段,可以在不同的查询中引用这些片段,增强了代码的一致性和可维护性。 - 修改时只需改动一处:如果基础字段发生变化,只需要在定义的SQL片段中进行一次修改,而不需要在每一个引用该片段的地方分别更新。
这样整理后的内容更清晰地反映了每个技术点的作用及其重要性,并提供了关于如何正确实现这些功能的具体指导。
5. 执行效果
当调用selectUserWithStats方法查询用户ID时:
- 目标:获取某个特定用户的基本信息及其最近3个月的订单活动情况。
生成的SQL
在AS后面,不可以直接写字段名,一定要通过数据库字段(column) → 映射到 → Java 对象属性(property)
比如:

SELECT
U.ID AS USER_ID,
U.USERNAME AS USER_NAME,
U.CREATE_TIME AS CREATE_TIME,
D.DEPT_NAME AS DEPARTMENT_NAME,
(SELECT COUNT(*)
FROM `order` O
WHERE O.USER_ID = U.ID
AND O.ORDER_TIME >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 MONTH)) AS ORDER_COUNT
FROM USER U
LEFT JOIN DEPARTMENT D ON U.DEPT_ID = D.ID
WHERE U.ID = #{userId}注意事项:
- 使用了反引号(``)包裹
order表名,因为order是SQL关键字。 - 使用
CURRENT_DATE()函数代替NOW()来确保只考虑日期部分(如果时间部分不重要)。 - 确保别名使用大写,与之前定义的
resultMap中的column属性匹配。
SQL子查询解释
(SELECT COUNT(*) FROM `order` o WHERE o.user_id = u.id AND o.order_time >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 MONTH)) AS ORDER_COUNT
- 作用:计算特定用户在过去3个月内生成的订单数量。
- 逻辑:从
order表(别名为o)中计数所有满足条件的记录:user_id与外部查询中的用户ID相匹配,且order_time在当前日期起往前推3个月的时间范围内。结果被命名为ORDER_COUNT。
查询结果映射
UserDTO{
userId=1001,
username="张三",
departmentName="技术部",
orderCount=5,
createTime=2023-01-15 // 假设时间为2023年1月15日
}
- 说明:此映射显示了如何将查询结果转换为Java对象。每个数据库字段通过MyBatis的
resultMap配置正确映射到相应的Java属性上,确保数据类型和命名的一致性。
6. 注意
在MyBatis的<result>标签中,column属性用于指定数据库表中的列名,而property属性则指定了对应的Java对象中的属性名。具体来说:
标签的作用
<result>:用于映射简单数据类型的结果集列到Java对象的属性上。它通常用于非主键字段的映射。
column属性
- 含义:表示数据库查询结果集中列的名称。这个名称必须与SQL查询返回的列名(包括别名)完全一致。
- 用途:告诉MyBatis从哪个列中获取数据,并将其映射到Java对象的指定属性上。
property 属性
这个属性指定了Java对象中的属性名。MyBatis会将查询结果集中对应列(通过column指定)的值赋给这个属性
- 数据库中的表(Table)会被映射为类(Class)。
- 表中的列(Column)会被映射为类的属性(Field/Property)。
- 表中的行(Row)会被映射为类的实例(Object)。
如果在MyBatis Mapper XML中遇到两个一样的column,比如说实体中先来了一个name字段,又来了一个newName字段, column属性都取名叫 NAME ,这不对,要给后来的newName字段取一个新名字
例:NEW_NAME
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
springboot使用jasypt加密库实现数据库加解密示例代码
这篇文章主要给大家介绍了关于springboot使用jasypt加密库实现数据库加解密的相关资料,Jasypt是一个用于配置文件加密的Java库,它可以用来加密和解密配置文件中的敏感信息,如数据库密码、API 密钥等,需要的朋友可以参考下2024-04-04
使用React和springboot做前后端分离项目的步骤方式
这篇文章主要介绍了使用React和springboot做前后端分离项目的步骤方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2023-08-08
SpringBoot整合RabbitMQ处理死信队列和延迟队列
这篇文章将通过示例为大家详细介绍SpringBoot整合RabbitMQ时如何处理死信队列和延迟队列,文中的示例代码讲解详细,需要的可以参考一下2022-05-05


最新评论