Maven+动态SQL(1.27)
一、Maven
1.依赖传递:
Maven 的Dependencies面板中,父级标签(比如org.junit.jupiter:junit-jupiter:5.8.2)是你直接在pom.xml中声明的依赖,而它下面的小标签(比如junit-jupiter-api)是这个依赖自动传递过来的 Jar 包(也就是 “依赖的依赖”)。

-
在
pom.xml里写了junit-jupiter:5.8.2这个依赖,这是直接依赖; -
junit-jupiter本身需要junit-jupiter-api、junit-jupiter-params等 Jar 包才能运行,这些就是传递依赖,会自动被 Maven 下载并引入。 -
灰色的 Jar 包:是被
<exclusions>排除的传递依赖,不会参与项目编译、运行。如果想恢复这个 Jar 包,只需要删除对应的<exclusion>标签,再刷新 Maven,它就会变回正常颜色~
2.排除依赖:
在pom.xml中,给对应的依赖加上<exclusions>标签,排除指定的传递依赖:
<!-- 假设这是你直接声明的依赖 -->
<dependency>
<groupId>cn.wolfcode</groupId>
<artifactId>Java61_Maven_2</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 排除这个依赖传递过来的lombok -->
<exclusions>
<exclusion>
<!-- 要排除的依赖的groupId和artifactId -->
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
</exclusions>
</dependency>
场景一:若只是不想用传递的依赖(不需要替换版本),则无需后续操作
- 如果只是单纯不想引入某个传递依赖(比如项目用不到、或避免冲突),不需要重新写 Jar 包,只需要排除即可。
场景二:若需要替换传递依赖的版本(核心场景)
- 如果传递依赖的版本有问题(比如版本太低 / 太高、有 bug),排除后必须重新声明你需要的版本,否则项目会缺少这个依赖导致报错。
补充说明
- 排除的是传递依赖(即依赖自动带的 Jar 包),不是你直接声明的依赖;
- 如果要排除多个传递依赖,可以在
<exclusions>里加多个<exclusion>标签。
3.作用范围:
scope 取值 |
主程序 | 测试程序 | 打包(运行) | 常见的依赖 |
|---|---|---|---|---|
compile |
Y | Y | Y | log4j |
test |
- | Y | - | junit |
provided |
Y | Y | - | servlet-api |
runtime |
- | Y | Y | JDBC驱动 |
4.更新依赖 索引:

实在不行的话,可以为项目清缓存,缓存失效得重新建立索引

二、MyBatis
1.参数处理:

--这里传参只允许传一个参数,若想传多个参数,则需要用Param注解实现:
(1)传递两个参数的修改流程概述:
①测试类修改:从传实体类对象,改为传实体类的两个属性值(如 emp.getId()、emp.getName());

②Mapper 接口修改:在方法参数前加 @Param 注解,给每个参数命名;

③Impl 实现类修改:调用 Mapper 接口方法时,传入这两个独立参数;

③XML 修改:去掉 parameterType,SQL 中直接用 @Param 定义的参数名(如 #{id})。

(2)核心方法:@Param 注解的作用
@Param 注解的核心是给参数 “起名字”,让 MyBatis 能识别多个独立参数 —— 因为 MyBatis 默认只能识别「单个参数」,如果直接传多个参数(如 queryById(Long id, String name)),MyBatis 会把参数封装成 Map,但默认 key 是 arg0/arg1(或 param1/param2),写 SQL 时用 #{arg0} 不直观且易出错。
用 @Param("id") 后:
- MyBatis 会把参数封装成
Map,key 是@Param里的名称(如id、name); - XML 中可以直接用
#{id}/#{name}引用参数,清晰且不易出错。
(3)为什么要使用 @Param 注解?
根本原因是解决 “多个独立参数的识别问题”:
- 如果不写
@Param,传多个参数时,XML 中只能用#{arg0}/#{param1}引用,可读性差; - 加了
@Param后,参数有了明确的名称,XML 中可以用直观的名字(如#{id}),代码更易维护; - 同时避免参数顺序变化导致的错误(比如参数顺序调整后,
arg0/arg1对应的参数会变,但@Param名称不变)。
总结来说,@Param 是 MyBatis 中传递多个独立参数的 “简洁方案”,核心作用是给参数命名,让 XML 能清晰识别多个参数。
(4)代码拆分解释:
-
第一步:获取 Mapper 接口的动态代理对象
javaEmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);sqlSession:MyBatis 的核心会话对象,负责与数据库交互;getMapper(EmployeeMapper.class):MyBatis 的动态代理机制,自动生成EmployeeMapper接口的实现类对象(你不需要写手动实现类);- 作用:拿到能执行接口方法的代理对象
mapper。
-
第二步:调用接口方法,执行数据库查询
Employee employee = mapper.queryById(id, name);mapper.queryById(id, name):调用代理对象的queryById方法,传入参数id和name;- MyBatis 会自动关联 XML 中
id="queryById"的<select>标签,执行对应的 SQL 语句; - 作用:执行查询,返回
Employee实体类对象(对应数据库查询结果)。
(5)OGNL表达式

--左边 mapper.xml 中的 parameterType:代表你传给 MyBatis 的参数(比如一个实体类、Map 或 @Param 封装的参数),它是 OGNL 表达式要操作的数据源。
--右边的 OGNL 表达式结构:用 “树状结构” 表示 OGNL 表达式的执行逻辑,其中 “虚根” 是整个表达式的顶层入口。
(6)什么是 OGNL 表达式?
OGNL(Object-Graph Navigation Language)即对象图导航语言,是 MyBatis 动态 SQL 的核心表达式引擎。
-
核心作用:通过简洁的语法,快速访问 Java 对象的属性、调用方法,判断条件是否成立。
-
常见用法
-
在 MyBatis 的
<if test="...">标签中,test里的条件就是 OGNL 表达式, -
比如:
<if test="ename != null and ename != ''"> and ename like concat('%', #{ename}, '%') </if> -
它能直接通过属性名(如
ename)访问对象的属性,无需写getEname()。
-
(7)什么是 OGNL 的 “虚根”?
在 OGNL 的执行机制中,“虚根”(Root Object)是一个顶层的上下文对象,是 OGNL 表达式的 “入口起点”。
- 作用:
- 统一入口:所有表达式都从虚根开始导航,访问它包含的属性和方法。
- 解决多参数问题:当你用
@Param传递多个参数时,MyBatis 会把这些参数封装到一个 Map 中,这个 Map 就是 OGNL 的虚根。表达式里的#{id}、#{name}本质是从这个虚根 Map 中获取值。 - 简化访问:如果传递的是单个实体类,这个实体类对象就是虚根,表达式里直接写属性名(如
ename)即可访问。
(8)若只有一个参数,参数的名称是不重要的,因为参数是根

2.#{}与${}的区别:
(1)#{}:15 ’abc‘ --->根据类型添加引号 安全
${}: 15 abc --->原文显示 不安全
(2)总结
- 日常开发优先用
#{}:它是安全的预编译占位符,能防注入,适合绝大多数场景。 - 仅在必须动态拼接 SQL 语句时用
${}:比如动态表名、排序字段等,但必须手动做安全校验。
三、动态Sql
1.复用 SQL 片段:

<sql> 标签:定义可复用的 SQL 片段
<sql id="emp_cols">
id ,name, sex ,sal
</sql>
- 作用:把查询时重复使用的列名(
id, name, sex, sal)定义成一个可复用的片段,并通过id="emp_cols"给它命名。 - 好处:后续需要修改列名时,只需改这一处,所有引用它的地方都会自动同步,避免了重复修改。
<include> 标签:引用定义好的 SQL 片段
<select id="queryById" resultType="Employee">
select
<include refid="emp_cols"/>
from employee where id = #{id}
</select>
- 作用:通过
refid="emp_cols"引用刚才定义的 SQL 片段,MyBatis 会在运行时把<include>替换成<sql>里的内容。
2.条件查询:
SELECT 字段1, 字段2, ...
FROM 表名
WHERE 条件1 [AND/OR 条件2] [AND/OR 条件3] ...
[ORDER BY 排序字段 排序方向]
[LIMIT 分页参数];
①新建qo包和EmpQO类:设置需要查询的字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmpQO {
private String ename;
private BigDecimal salStart;
private BigDecimal salEnd;
private String key;//关键字 多个字段中,只要出现,就符合要求
}
②test类:
//条件查询
@Test
public void test3() throws ParseException {
EmpQO qo = new EmpQO();
//只需要set你想要查询的字段
//整形字段-->begin and String-->模糊查询 like '%关键字%' _
qo.setEname("张三");
qo.setSalStart(new BigDecimal(2000.0));
qo.setSalEnd(new BigDecimal(7000.0));
qo.setKey("三");
List<Emp> empList = empMapper.queryByCondtion(qo);
System.out.println(empList);
}
③Mapper接口:
int addList(List<Emp> list);
④Mapper实现类:
@Override
public int addList(List<Emp> list) {
SqlSession sqlSession = MyBatisTool.getSqlSession();
int r = sqlSession.insert("cn.wolfcode.mapper.EmpMapper.addList", list);
sqlSession.commit();
MyBatisTool.close(sqlSession);
return r;
}
⑤xml文件:
<select id="queryByCondtion" resultType="cn.wolfcode.domain.Emp">
select
<include refid="emp_cols"/>
from emp
<where>
<if test="ename != null and ename != ''">
and ename like concat('%',#{ename},'%')
</if>
<if test="salStart != null and salStart != ''">
and sal >= #{salStart}
</if>
<if test="salEnd != null and salEnd != ''">
and sal <= #{salEnd}
</if>
</where>
</select>
where标签的作用:
1. 自动判断是否需要生成 WHERE
- 如果它包裹的
<if>条件有任何一个成立,MyBatis 会自动在 SQL 开头加上WHERE关键字。 - 如果所有
<if>条件都不成立,它就不会生成WHERE,避免出现SELECT ... FROM emp WHERE这种语法错误。
2. 自动处理多余的 AND / OR
- 当第一个生效的
<if>条件以AND或OR开头时,<where>会自动把这个多余的前缀去掉。 - 比如你的代码里每个
<if>都写了and ...,<where>会确保最终 SQL 不会出现WHERE AND ...这种错误写法。
每个条件都默认带 and,不管它是不是第一个生效的条件,<where> 会帮我们处理掉多余的那个 and。
补充:

2.为什么if标签下可以写>=不能写<=?
XML 会把 < 当成标签的开始符号,所以:
- 在标签的属性值里(比如
<if test="...">中的test属性),如果直接写<或<=,XML 解析器会误以为是新标签的开始,导致解析报错。 - 在标签的内容区(比如
<if>标签内部的 SQL 代码),>=可以直接写,因为>不会被误解析;但<=里的<仍然会触发解析错误,所以必须转义。
3.批量增加:
INSERT INTO 表名 (字段1, 字段2, 字段3, ...)
VALUES
(值1-1, 值1-2, 值1-3, ...), -- 第一条数据
(值2-1, 值2-2, 值2-3, ...), -- 第二条数据
(值3-1, 值3-2, 值3-3, ...); -- 第三条数据(最后一条末尾只写分号)
①test类:
EmpMapper empMapper = new EmpMapperImpl();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//批量添加
@Test
public void test() throws ParseException {
Date date = sdf.parse("2026-01-26");
List<Emp> list = List.of(
new Emp(1001L,"张三","摸鱼",7839L,date,6000.0,1000.0,20L),
new Emp(1002L,"李四","看小说",7839L,date,7000.0,1500.0,20L),
new Emp(1003L,"王五","睡觉",7839L,date,8000.0,2000.0,20L)
);
int r = empMapper.addList(list);
if(r>0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
}
②mapper接口:
int addList(List<Emp> list);
③mapper实现类:
@Override
public int addList(List<Emp> list) {
SqlSession sqlSession = MyBatisTool.getSqlSession();
int r = sqlSession.insert("cn.wolfcode.mapper.EmpMapper.addList", list);
sqlSession.commit();
MyBatisTool.close(sqlSession);
return r;
}
④xml文件:
<insert id="addList">
<!-- 1. emp 是数据库表名 -->
insert into emp (
<!-- 2. 括号内的都是数据库表的字段名 -->
empno,ename,job,mgr,hiredate,sal,comm,deptno
) values
<!-- 3. list 是方法传入的List集合参数名 -->
<foreach collection="list"
<!-- 4. emp 是遍历List时单个Emp对象的别名 -->
item="emp"
separator=",">
<!-- 5. emp.empno/ename/... 是Emp实体类的属性名 -->
(#{emp.empno},#{emp.ename},#{emp.job},#{emp.mgr},#{emp.hiredate},#{emp.sal},#{emp.comm},# {emp.deptno})
</foreach>
</insert>
1. collection="list"
-
作用:指定你要遍历的数据源(集合)名称。
-
具体含义
:
- 这里的
list是 MyBatis 的默认值—— 当你的 Mapper 接口方法参数是一个List集合(且没有加@Param注解命名)时,MyBatis 会默认把这个集合命名为list,所以<foreach>要通过collection="list"找到这个集合。 - 举个例子:如果你的接口方法是
int addList(List<Emp> empList);,因为参数是 List 且无@Param,所以 XML 里必须写collection="list"。 - 补充:如果接口加了
@Param注解(比如int addList(@Param("empList") List<Emp> empList);),那collection就要改成empList,和注解里的名称一致。
- 这里的
2. item="emp"
-
作用:给遍历集合时的单个元素起别名。这个别名和你自己建的 Emp 类没有直接关联,不需要和 Emp 类名一致
真正需要一致的是什么?
唯一需要和 Emp 对象一致的,是
#{别名.属性名}里的属性名,而不是别名本身: -
具体含义
:
- 遍历
list这个集合时,每循环一次会取出一个Emp对象,item="emp"就是给这个 “当前取出的 Emp 对象” 起个名字叫emp。 - 后续
#{emp.empno}、#{emp.ename}里的emp,就是引用这个别名,用来获取单个 Emp 对象的属性值。 - 类比理解:就像 Java 里的增强 for 循环
for (Emp emp : empList),这里的item="emp"对应循环里的Emp emp,collection="list"对应empList。
- 遍历
拓展:
open 和 close 属性的作用
| 属性 | 作用 |
|---|---|
open |
指定 <foreach> 遍历内容开头要拼接的字符串 |
close |
指定 <foreach> 遍历内容结尾要拼接的字符串 |
例如:
insert into emp (
empno,ename,job,mgr,hiredate,sal,comm,deptno
) values
<foreach collection="list" item="emp" separator=", open="(" close=")">
#{emp.empno},#{emp.ename},#{emp.job},#{emp.mgr},#{emp.hiredate},#{emp.sal},#{emp.comm},#{emp.deptno}
</foreach>
4.批量删除:
DELETE FROM 表名
WHERE 主键字段 IN (值1, 值2, 值3, ...);
①test类:
//批量删除
@Test
public void test1() {
Long [] ids = {1001L,1002L,1003L};
int r = empMapper.deleteByIds(ids);
if(r>0){
System.out.println("删除成功!");
}else{
System.out.println("删除失败!");
}
}
②mapper接口:
int deleteByIds(Long[] ids);
③mapper实现类:
@Override
public int deleteByIds(Long[] ids) {
SqlSession sqlSession = MyBatisTool.getSqlSession();
int r = sqlSession.delete("cn.wolfcode.mapper.EmpMapper.deleteByIds", ids);
sqlSession.commit();
MyBatisTool.close(sqlSession);
return r;
}
④xml文件:
<delete id="deleteByIds">
delete from emp
where empno in (
<foreach collection="array" item="empno" separator=",">
#{empno}
</foreach>
)
</delete>
1.collection="array" 的含义
-
核心作用:指定你要遍历的数据源(参数)名称。
-
具体解释:
array是 MyBatis 的默认关键字—— 当你的 Mapper 接口方法参数是一个数组(比如int[] empnos或Integer[] empnos),且没有加@Param注解命名时,MyBatis 会默认把这个数组参数命名为array,所以<foreach>要通过collection="array"找到这个数组。- 举个例子:如果你的接口方法是
int deleteByIds(Integer[] empnos);,因为参数是数组且无@Param,所以 XML 里必须写collection="array";如果加了注解(比如int deleteByIds(@Param("empnos") Integer[] empnos);),那collection就要改成empnos。
2.item="empno" 的含义
- 核心作用:给遍历数组时的单个元素起别名。
- 具体解释:
- 遍历
array这个数组时,每循环一次会取出一个数组元素(比如 1001、1002、1003),item="empno"就是给这个 “当前取出的单个主键值” 起个名字叫empno。 - 后续
#{empno}里的empno,就是引用这个别名,用来获取数组中单个的员工编号值。 - 类比 Java 循环:就像
for (Integer empno : empnos) {},这里的item="empno"对应循环里的Integer empno,collection="array"对应empnos数组。
- 遍历
注意区分:
-
遍历基本类型(数组 / 集合):元素本身就是目标值,直接写
#{别名}(比如#{empno}); -
遍历实体类对象(集合):元素是对象,需要写
#{别名.属性名}(比如#{emp.empno})。
5.局部更新:字段为null-->不更新 字段为null-->更新
UPDATE 表名
SET
字段1 = 值1,
字段2 = 值2,
-- 只写需要更新的字段,不需要的字段不写
WHERE
更新条件;
①test类:
//局部更新
@Test
public void test2() throws ParseException {
Date date = sdf.parse("2026-01-27");
Emp emp = new Emp(1000L,"张三","摸鱼",7839L,date,null,null,null);
int r = empMapper.updatePartten(emp);
if(r>0){
System.out.println("更新成功!");
}else{
System.out.println("更新失败!");
}
}
②mapper接口:
int updatePartten(Emp emp);
③mapper实现类:
@Override
public int updatePartten(Emp emp) {
SqlSession sqlSession = MyBatisTool.getSqlSession();
int r = sqlSession.delete("cn.wolfcode.mapper.EmpMapper.updatePartten", emp);
sqlSession.commit();
MyBatisTool.close(sqlSession);
return r;
}
④xml文件:
<update id="updatePartten">
update emp
<set>
<!-- 普通字符串/数值字段:可以判断 != '' and != null -->
<if test="ename != null and ename !=''">
ename = #{ename},
</if>
<if test="job != null and job !=''">
job = #{job},
</if>
<if test="mgr != null and mgr !=''">
mgr = #{mgr},
</if>
<if test="hiredate != null">
hiredate = #{hiredate},
</if>
</set>
where empno = #{empno}
</update>
1.补充:末尾的逗号为什么不会报错?
你可能注意到这行末尾有个逗号 ,,但不用担心语法错误:
<set> 标签会自动处理多余的逗号 —— 比如最终只有 hiredate 字段要更新时,<set> 会把 hiredate = #{hiredate}, 末尾的逗号去掉,生成 set hiredate = ?,避免 SQL 语法错误。
2.<set> 标签的核心作用
(1) 自动处理多余的逗号(最关键)
在动态更新时,每个 <if> 里的更新字段末尾都会加逗号(比如 ename = #{ename},),如果最后一个生效的字段也带逗号,会生成 SET ename = '张三', 这种语法错误的 SQL。
<set> 会自动识别并删除最后一个字段后面多余的逗号,保证 SQL 语法正确。
(2) 动态控制 SET 关键字的生成
- 如果
<set>包裹的<if>有任意一个条件成立(有字段要更新),MyBatis 会自动在 SQL 开头加上SET关键字; - 如果所有
<if>条件都不成立(没有字段要更新),<set>就不会生成SET,避免出现UPDATE emp SET WHERE empno = ?这种语法错误。
6.RequestMap标签:
(1)清核心问题:为什么需要 resultMap?
resultType="Employee" 能直接映射的前提是:数据库表字段名 = 实体类属性名(比如表字段 emp_name ≠ 实体类 empName 就会映射失败);如果有计算字段(比如 sal*12 as annual_sal),resultType 也无法直接映射到实体类的 annualSal 属性。
(2)解决两个小问题:
1. 字段名不一致:两种解决方案
方案 1:SQL 取别名(临时解决,适合简单场景)
如果表字段是 emp_name,实体类属性是 empName,可以在 SQL 里给字段取别名,让别名和属性名一致:
<select id="getEmpById" resultType="Employee">
<!-- 表字段emp_name → 别名empName(和实体类属性一致) -->
select empno, emp_name as empName, job, sal from emp where empno = #{empno}
</select>
方案 2:resultMap 映射(推荐,一劳永逸)
如果多个查询都需要映射,不用每次写别名,直接定义 resultMap:
<!-- 1. 定义resultMap:给MyBatis写映射规则 -->
<resultMap id="EmpResultMap" type="Employee">
<!-- id:映射主键字段(可选,但推荐写) -->
<id column="empno" property="empno"/>
<!-- result:映射普通字段 → column=表字段名,property=实体类属性名 -->
<result column="emp_name" property="empName"/>
<result column="job" property="job"/>
<result column="sal" property="sal"/>
</resultMap>
<!-- 2. 查询时引用resultMap -->
<select id="getEmpById" resultMap="EmpResultMap">
<!-- SQL不用取别名,直接写表字段名 -->
select empno, emp_name, job, sal from emp where empno = #{empno}
</select>
2. 有计算字段:必须用 resultMap(或别名 +resultType)
比如要计算年薪(sal*12),实体类有 annualSal 属性,两种写法:
方案 1:SQL 别名 + resultType(临时)
<select id="getEmpAnnualSal" resultType="Employee">
<!-- 计算字段取别名,和实体类annualSal一致 -->
select empno, emp_name as empName, sal, sal*12 as annualSal from emp where empno = #{empno}
</select>
方案 2:resultMap(推荐,计算字段也能映射)
<!-- 定义resultMap:新增计算字段的映射 -->
<resultMap id="EmpWithAnnualSalMap" type="Employee">
<id column="empno" property="empno"/>
<result column="emp_name" property="empName"/>
<result column="sal" property="sal"/>
<!-- column=SQL里的计算字段别名,property=实体类属性 -->
<result column="annual_sal" property="annualSal"/>
</resultMap>
<!-- 查询时SQL写计算字段,不用和属性名一致 -->
<select id="getEmpAnnualSal" resultMap="EmpWithAnnualSalMap">
select empno, emp_name, sal, sal*12 as annual_sal from emp where empno = #{empno}
</select>
(3)resultMap 详细拆解(易懂版)
1. resultMap 核心结构
<!-- id:resultMap的唯一标识(查询时要引用);type:要映射的实体类全类名(或别名) -->
<resultMap id="自定义ID" type="实体类全类名/别名">
<!-- 1. 主键映射(可选,标记主键字段,提升性能) -->
<id column="数据库表字段名/计算字段别名" property="实体类属性名"/>
<!-- 2. 普通字段映射(核心) -->
<result column="数据库表字段名/计算字段别名" property="实体类属性名"/>
<!-- 3. 高级用法:关联查询/集合查询(后续学联表时用) -->
<association/><!-- 一对一关联,比如员工关联部门 -->
<collection/><!-- 一对多关联,比如部门关联多个员工 -->
</resultMap>
2. 核心参数说明(关键!)
| 参数 | 含义 |
|---|---|
id(resultMap 的属性) |
给这个映射规则起个名字,查询时用 resultMap="这个名字" 引用 |
type |
要映射的实体类(比如 com.xxx.pojo.Employee,或配置别名后写 Employee) |
column |
数据库查询结果里的 “列名”(可以是表原字段名、SQL 别名、计算字段别名) |
property |
实体类里的 “属性名”(比如 empName、annualSal) |
浙公网安备 33010602011771号