news 2026/9/27 6:19:24

MyBatis增删改查与参数处理:从流程复用到安全查询实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis增删改查与参数处理:从流程复用到安全查询实战

第一部分:流程复用思路

1.1 核心思想

⭐老师强调:首个流程走通后,后续流程无需重复写前期逻辑,因为基础已铺垫好,只需编写并调用核心业务段即可。

流程复用示例:

流程需要写的代码
第一个流程(findAll)完整写加载配置、创建工厂、会话、映射、查询、处理结果
后续流程(findById、insert等)只写接口方法 + 映射文件 + 测试调用

⭐老师强调:区分"写逻辑"和"调业务"——第一个流程要完整写查找等逻辑,后续只管写核心段、调核心业务,不必重铺前文。

第二部分:增删改查完整实现

2.1 接口定义(StudentDao.java)

package com.qcby.dao; import com.qcby.entity.Person; import com.qcby.entity.Student; import org.apache.ibatis.annotations.Param; import java.util.List; public interface StudentDao { // 查找全部学生信息 List<Student> findAll(); // 根据id查找学生信息 Student findById(int id); // 插入 int insert(Student student); // 删除 int delete(int id); // 修改 int update(Student student); // 模糊查找 List<Student> findByName(String name); // 分页查找 List<Student> findStudent(@Param("name123") String name, @Param("start") int start, @Param("limit") int limit); // 包装对象参数 List<Student> findAllPerson(Person person); }

2.2 映射文件(StudentDao.xml)

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.qcby.dao.StudentDao"> <!-- 查找全部(使用resultMap映射) --> <select id="findAll" resultMap="demo"> select * from student </select> <resultMap id="demo" type="com.qcby.entity.Student"> <result column="id" property="id" jdbcType="INTEGER" javaType="java.lang.Integer"/> <result column="sname" property="name" jdbcType="VARCHAR" javaType="java.lang.String"/> <result column="sage" property="age"/> <result column="sex" property="sex"/> </resultMap> <!-- 根据id查找 --> <select id="findById" parameterType="java.lang.Integer" resultType="com.qcby.entity.Student"> select * from student where id = #{id} </select> <!-- 插入(带selectKey获取自增id) --> <insert id="insert" parameterType="com.qcby.entity.Student"> insert into student(name,age,sex) values(#{name},#{age},#{sex}) <selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER"> select last_insert_id() </selectKey> </insert> <!-- 删除 --> <delete id="delete" parameterType="com.qcby.entity.Student"> delete from student where id = #{id} </delete> <!-- 修改 --> <update id="update" parameterType="com.qcby.entity.Student"> update student set name = #{name},age=#{age},sex=#{sex} where id = #{id} </update> <!-- 模糊查找(推荐用concat) --> <select id="findByName" parameterType="java.lang.String" resultType="com.qcby.entity.Student"> select * from student where name like concat("%",#{name},"%") </select> <!-- 分页查找(多参数用@Param注入) --> <select id="findStudent" resultType="com.qcby.entity.Student"> select * from student where name like concat("%",#{name123},"%") limit #{start},#{limit} </select> <!-- 包装类对象参数 --> <select id="findAllPerson" parameterType="com.qcby.entity.Person" resultType="com.qcby.entity.Student"> select * from student where age = #{student.age} </select> </mapper>

第三部分:#{}与${}的区别

3.1 核心对比

对比#{}${}
本质占位符赋值,预编译字符串拼接
防SQL注入✅ 能❌ 不能
简单类型变量名任意写低版本必须value,高版本任意写
字符串字段自动加''不会自动加''
实体类对象必须是引用类型的属性必须是引用类型的属性

⭐老师强调:#{}本质同JDBC预编译占位符,把用户输入与指令分离,能挡SQL注入;${}是纯拼接,先拼成完整SQL再编译执行,你传什么就拼什么去查。

3.2 示例说明

<!-- #{}:预编译占位,安全 --> <select id="findById" parameterType="java.lang.Integer" resultType="com.qcby.entity.Student"> select * from student where id = #{id} </select> <!-- ${}:字符串拼接,有注入风险 --> <select id="findByName" parameterType="java.lang.String" resultType="com.qcby.entity.Student"> select * from student where name like "%${name}%" </select> <!-- ✅ 推荐:concat + #{},既安全又能模糊查询 --> <select id="findByName" parameterType="java.lang.String" resultType="com.qcby.entity.Student"> select * from student where name like concat("%",#{name},"%") </select>

⭐老师强调:#{}写在SQL引号内无法将参数塞入占位符,必须写在引号外。涉及引号时框架会自动加引号。

3.3 字符串字段的引号处理

<!-- ${}:不会自动加引号,字符串字段必须手动加 --> insert into student(name,age,sex) values('${name}',${age},'${sex}') <!-- #{}:会自动加引号,无需手动处理 --> insert into student(name,age,sex) values(#{name},#{age},#{sex})

第四部分:参数类型详解

4.1 四种入参类型

⭐老师强调:parameterType入参类型分四类。

类型说明示例
简单数据类型8个基本类型+包装类+Stringjava.lang.Integer、java.lang.String
实体类对象类型整个对象作为参数com.qcby.entity.Student
多个数据多个参数用@Param注入@Param("name") String name, @Param("start") int start
包装对象类型实体类嵌套实体类com.qcby.entity.Person(内含Student)

4.2 简单类型

<select id="findById" parameterType="java.lang.Integer" resultType="com.qcby.entity.Student"> select * from student where id = #{id} </select>

⭐老师强调:简单类型参数括号里变量名可任意写,都能识别。推荐写全称如java.lang.Integer更规范。String本质是引用类型,但因太常用,传参时和int无区别。

4.3 实体类对象类型

<insert id="insert" parameterType="com.qcby.entity.Student"> insert into student(name,age,sex) values(#{name},#{age},#{sex}) </insert>

⭐老师强调:括号里的名字必须与实体类字段名严格匹配,随便写demo这类不存在的字段就识别不到。传入的是对象,就得按对象结构取属性。

4.4 多个参数(@Param注入)

// 接口 List<Student> findStudent(@Param("name123") String name, @Param("start") int start, @Param("limit") int limit);
<!-- 映射文件 --> <select id="findStudent" resultType="com.qcby.entity.Student"> select * from student where name like concat("%",#{name123},"%") limit #{start},#{limit} </select>

⭐老师强调:多参数时#{}无法直接绑定,必须用@Param注解注入——注解里写什么名,#{}里就用什么名。多个参数写法直接写在方法括号中即可。

4.5 包装对象类型(实体类嵌套)

Person实体类:

package com.qcby.entity; public class Person { private Student student; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } @Override public String toString() { return "Person{student=" + student + '}'; } }

映射文件:

<select id="findAllPerson" parameterType="com.qcby.entity.Person" resultType="com.qcby.entity.Student"> select * from student where age = #{student.age} </select>

测试代码:

@Test public void findAllPerson() { Person person = new Person(); Student student = new Student(); student.setAge(25); student.setName("小宇"); student.setSex("男"); student.setId(28); person.setStudent(student); List<Student> students = mapper.findAllPerson(person); for (Student s : students) { System.out.println(s); } }

⭐老师强调:当入参是包含嵌套类型的实体类(如Person里嵌了Student),若想用Student里的字段做条件,直接用点形式访问即可,如#{student.age}。难点在于理解嵌套对象字段的访问路径——不像平铺字段直接取,得明确层级。

第五部分:resultMap结果集映射

5.1 为什么需要resultMap

⭐老师强调:当数据库字段和实体属性名称不匹配(如库里是sname/sage,代码里是name/age)时,不能用resultType,必须改用resultMap做结果集映射。

5.2 使用方式

<resultMap id="demo" type="com.qcby.entity.Student"> <!-- column: 数据库中的字段名称 --> <!-- property: 实体类中的名字 --> <!-- jdbcType: 数据库中的字段类型 --> <!-- javaType: 实体类中数据的类型 --> <result column="id" property="id" jdbcType="INTEGER" javaType="java.lang.Integer"/> <result column="sname" property="name" jdbcType="VARCHAR" javaType="java.lang.String"/> <result column="sage" property="age"/> <result column="sex" property="sex"/> </resultMap> <select id="findAll" resultMap="demo"> select * from student </select>

5.3 属性说明

属性说明是否必须
column数据库字段名✅ 必须
property实体类属性名✅ 必须
jdbcType数据库字段类型(大写)❌ 可省略
javaType实体类数据类型❌ 可省略

⭐老师强调:jdbcType和javaType不写也能自动识别,写了反而容易出错,所以尽量别写。column和property是映射核心不能省。

第六部分:插入后获取自增id

6.1 selectKey的使用

<insert id="insert" parameterType="com.qcby.entity.Student"> insert into student(name,age,sex) values(#{name},#{age},#{sex}) <selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER"> select last_insert_id() </selectKey> </insert>

6.2 属性说明

属性说明
keyProperty把查出的主键放到哪个属性(如Student的id)
keyColumn数据库主键名(如id)
resultType主键类型
orderAFTER=插入后查;BEFORE=插入前查

⭐老师强调:

  • order的区分:设了自动递增用AFTER(插入后自动把新id返到对象里);没设自动递增用BEFORE(添加前先查id)
  • 为什么不查max(id):单线程下查最大id可行,但并发场景下你插入完、查最大id前,可能别的线程也插了数据。必须用返回刚插入id的机制才可靠

6.3 测试代码

@Test public void insert() { Student student = new Student(); student.setName("小红"); student.setAge(18); student.setSex("女"); int code = mapper.insert(student); // 事务提交 session.commit(); System.out.println(code); // 受影响行数 System.out.println(student.getId()); // 刚插入的id System.out.println(student); }

⭐老师强调:执行后返回两样:一是受影响行数;二是最新添加记录的id(直接存入Student对象,不单独返回)。

第七部分:事务提交

7.1 何时需要事务提交

操作是否需要commit
增删改✅ 必须
查询❌ 不需要

⭐老师强调:只有增删改这类改动数据库的操作才需要事务提交,查找不用。不加事务提交,数据无法真正写入库里。

7.2 测试示例

@Test public void delete() { int code = mapper.delete(26); session.commit(); // 必须提交 System.out.println(code); } @Test public void update() { Student student = new Student(); student.setName("小宇"); student.setAge(20); student.setSex("男"); student.setId(28); int code = mapper.update(student); session.commit(); // 必须提交 System.out.println(code); }

⭐老师强调:修改操作时name、age、sex三个参数必须全传,少一个就会出错;后续可加动态SQL来避免这个问题。

第八部分:日志配置

8.1 settings配置

<configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <environments default="mysql"> ... </environments> <mappers> ... </mappers> </configuration>

⭐老师强调:MyBatis配置文件有严格的标签顺序约束——settings必须最前,接着是environments,最后是mappers。乱序会报错。

8.2 日志效果

配置后可以看到SQL执行全过程:

==> Preparing: select * from student where id = ? ==> Parameters: 10(Integer) <== Columns: id, sname, sage, sex

⭐老师强调:日志体现预编译防注入机制——先有?占位符,再传入值塞进占位处。

第九部分:测试类完整代码

package com.qcby; import com.qcby.dao.StudentDao; import com.qcby.entity.Person; import com.qcby.entity.Student; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class StudentTest { private InputStream inputStream = null; private SqlSession session = null; private StudentDao mapper = null; @Before // 前置通知 public void init() throws IOException { // 加载主配置文件 inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 创建SqlSession对象 session = sqlSessionFactory.openSession(); // 通过sqlsession创建接口的代理对象 mapper = session.getMapper(StudentDao.class); } @After public void destroy() throws IOException { session.close(); inputStream.close(); } @Test public void run() { List<Student> students = mapper.findAll(); for (Student student : students) { System.out.println(student); } } @Test public void findById() { Student student = mapper.findById(10); System.out.println(student); } @Test public void insert() { Student student = new Student(); student.setName("小红"); student.setAge(18); student.setSex("女"); int code = mapper.insert(student); session.commit(); // 事务提交 System.out.println(code); System.out.println(student.getId()); System.out.println(student); } @Test public void delete() { int code = mapper.delete(26); session.commit(); // 事务提交 System.out.println(code); } @Test public void update() { Student student = new Student(); student.setName("小宇"); student.setAge(20); student.setSex("男"); student.setId(28); int code = mapper.update(student); session.commit(); // 事务提交 System.out.println(code); } @Test public void findByName() { List<Student> students = mapper.findByName("李"); for (Student student : students) { System.out.println(student); } } @Test public void findStudent() { List<Student> students = mapper.findStudent("李", 1, 3); for (Student student : students) { System.out.println(student); } } @Test public void findAllPerson() { Person person = new Person(); Student student = new Student(); student.setAge(25); student.setName("小宇"); student.setSex("男"); student.setId(28); person.setStudent(student); List<Student> students = mapper.findAllPerson(person); for (Student student1 : students) { System.out.println(student1); } } }

附录一:核心对比速查表

#{}vs${}

对比#{}${}
本质占位符预编译字符串拼接
防注入✅❌
简单类型命名任意写低版本必须value
字符串引号自动加不自动加
实体类属性必须是引用类型属性必须是引用类型属性

入参类型速查

类型parameterType写法示例
简单类型java.lang.Integer/java.lang.StringfindById
实体类com.qcby.entity.Studentinsert
多参数无需parameterType,用@ParamfindStudent
包装对象com.qcby.entity.PersonfindAllPerson

出参类型速查

操作resultType/resultMap说明
查单条resultType实体类
查多条resultType实体类(框架自动组装List)
字段不匹配resultMap手动指定column→property
增删改无需声明固定返回int
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 6:18:52

浪涌抗扰度试验全解析:从标准到整改的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 6:18:26

STC8G1K08低功耗实战:STOP模式+定时器唤醒实现1μA待机电流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 6:18:20

STM32参考方案资源地图:从官方到开源社区的高效检索指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 6:12:48

华为无线维护L2核心:AP上线、无线认证与漫游调优实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 6:11:25

EPLAN欧姆龙部件宏导入与实战:从获取到避坑全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华