前期回顾:
MyBatis的开发有两种方式:
1.注解
2.XML
上一小节,分享了使用注解的方式,接下来这一节将分享XML的方式。
使用MyBatis的注解方式,主要是来完成一些简单的增删改查功能,但如果需要实现复杂的SQL功能,更推荐使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中
MyBatis XML的方式需要以下两步:
application.yml配置:
# 数据库连接配置spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mybatis_test? characterEncoding=utf8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver#配置mybatis xml的文件路径,在resources/mapper创建所有表的xml文件mybatis: mapper-locations:classpath:mapper/**Mapper.xml
application.properties配置:
#驱动类名称spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#数据库链接的urlspring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false#连接数据库的用户名spring.datasource.username=root#链接数据库的密码spring.datasource.password=root#配置mybatis xml文件路径,在resources/mapper创建所有表的xml文件mybatis.mapper-locations=classpath:mapper/**Mapper.xml
持久层代码分为两部分:

这里我们先添加一个插件:

数据持久层的定义接口:
package com.MybatisDemo.demo.mapper;import com.MybatisDemo.demo.model.UserInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface UserInfoXMLMapper {List<UserInfo> queryAllUser();}<?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">//这里的namespace根据实际情况写<mapper namespace="com.MybatisDemo.demo.mapper.UserInfoXMLMapper"></mapper>
创建UserInfoXMLMapper.xml,路径参考properties/yml中的配置

查询所有用户的具体实现:

将自动生成:

select username,`password`,age,gender,phone from user_info
@SpringBootTestclass UserInfoXMLMapperTest { @Autowired private UserInfoXMLMapper userInfoXMLMapper; @Test void queryAllUser(){ List<UserInfo> userInfoXMLList =userInfoXMLMapper.queryAllUser(); System.out.println(userInfoXMLList); }}
以上是具体操作,接下来对标签进行说明

—id:是和 Interface (接口)中定义的方法名称⼀样的,表示对接口的具体实现方法。
—resultType:是返回的数据类型,也就是开头我们定义的实体类.
在 MyBatis XML 中,插入操作使用 <insert> 标签。该标签的 id 属性需要与 Mapper 接口中定义的方法名保持一致。
Mapper 接口方法定义:
// 插入用户信息Integer insertUser(UserInfo userInfo);
XML 映射配置:
<insert id="insertUser"> insert into user_info (username, password, age, gender, phone) values (#{username}, #{password}, #{age}, #{gender}, #{phone})</insert>测试方法:
@Test void insertUser() { UserInfo user = new UserInfo(); user.setUsername("hhh1"); user.setPassword("123456"); user.setAge(20); user.setGender(2); user.setPhone("1235"); Integer row = userInfoXMLMapper.insertUser(user); System.out.println("添加数据条数:" + row); }运行展示:

如果使用@Param设置参数名称,使用方法和注解类似:
//UserInfoXMLMapper接⼝:Integer insertUser(@Param("userInfo") UserInfo userInfo);//UserInfoXMLMapper.xml实现:<insert id="insertUser">insert into user_info (username, password, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})</insert>返回自增id:接口定义不变,Mapper.xml实现 设置useGeneratedKeys和keyProperty属性
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into user_info (username, password, age, gender, phone) values (#{username}, #{password}, #{age}, #{gender}, #{phone})</insert>删除操作使用 <delete> 标签,同样通过 id 属性与方法绑定。
Mapper 接口方法定义:
// 根据用户ID删除用户Integer deleteUser(Integer id);
XML 映射配置:
<delete id="deleteUserById"> delete from user_info where id = #{id}</delete>测试方法:
@Test void deleteUserByID() { Integer rows =userInfoXMLMapper.deleteUserByID(6); System.out.println("删除影响行数:" + rows); }运行展示:

更新操作使用 <update> 标签,用于修改数据库中已有的记录。
Mapper 接口方法定义:
// 根据用户ID更新用户信息Integer updateUser(UserInfo userInfo);
XML 映射配置:
<update id="updateUser"> update user_info set username = #{username} where id = #{id}</update>测试方法:
@Test void updateUser() { UserInfo user = new UserInfo(); user.setId(1); user.setUsername("h1"); Integer rows = userInfoXMLMapper.updateUser(user); System.out.println("修改行数:" + rows); }运行展示:

查询操作使用<sekect>标签,这在上一节已初步接触。这里补充更详细的用法和参数传递。
Mapper 接口方法定义(带参数查询):
// 根据用户名查询用户UserInfo selectUserByName(String username);
XML 映射配置:
<select id="select"> select id, username, password, age, gender, phone, delete_flag, create_time, update_time from user_info</select>
运行展示:

解决方法和类注解类似:
1.起别名
2.结果映射
3.开启驼峰命名
其中1,3的解决方法和注解一样,感兴趣的可以看上一篇文章,接下来介绍xml中的结果映射
<resultMap id="BaseMap" type="com.MybatisDemo.demo.model.UserInfo"> <id column="id" property="id"></id> <result column="delete_flag" property="deleteFlag"></result> <result column="create_time" property="createTime"></result> <result column="update_time" property="updateTime"></result></resultMap><select id="queryAllUser" resultMap="BaseMap">select id, username,password, age, gender, phone, delete_flag, create_time, update_time from user_info</select>
通过 XML 配置 SQL,使得 SQL 语句与 Java 代码分离,结构更清晰,尤其便于维护复杂的动态 SQL
多表查询和单表查询类似,只是sql不同而已
数据准备:
DROP TABLE IF EXISTS articleinfo;CREATE TABLE articleinfo ( id INT PRIMARY KEY auto_increment, title VARCHAR ( 100 ) NOT NULL, content TEXT NOT NULL, uid INT NOT NULL, delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除', create_time DATETIME DEFAULT now(), update_time DATETIME DEFAULT now() ) DEFAULT charset 'utf8mb4';//插入测试数据INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1);
数据查询 需求: 根据uid查询作者的名称等相关信息:
select ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender from articleinfo ta left join user_info tb on ta.uid = tb.id where ta.id =1
对应model:
@Datapublic class ArticleInfo { private Integer id; private String title; private String content; private Integer uid; private Integer deleteFlag; private Date createTime; private Date updateTime; //⽤⼾相关信息 private String username; private Integer age; private Integer gender;}接口定义:
@Mapperpublic interface ArticleInfoMapper { @Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " + "FROM articleinfo ta LEFT JOIN user_info tb ON ta.uid = tb.id " + "WHERE ta.id = #{id}") ArticleInfo queryUserByUid(Integer id);}#{} 的使用示例:
@Select("select username, password, age, gender, phone from user_info where id = #{id}") UserInfo queryById(Integer id);
我们发现我们输入的参数并没有在后面拼接,id值使用 ?进行占位,这中sql我们称为“预编译sql”
${} 的使用示例:
@Select("select username, password, age, gender, phone from user_info where id = ${id}") UserInfo queryById(Integer id);
#{} 的使用示例:
@Select("select username, password, age, gender, phone from user_info where username = #{name}") UserInfo queryByname(String name);
${} 的使用示例:
@Select("select username, password, age, gender, phone from user_info where username = ${name}") UserInfo queryByname(String name);
可以看到,这次参数依然是直接拼接在sql语句中了,但是字符串作为参数,需要添加单引号,使用${ }不会拼接引号,导致程序报错
修改代码为:
@Select("select username, password, age, gender, phone from user_info where username = '${name}'") UserInfo queryByname(String name);
从上面例子可以看出:
#{} 使用的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进行编译, 然后把参数填充到SQL语句中. #{} 会根据参数类型, ⾃动拼接引号 '' . ${} 会直接进行字符替换, ⼀起对SQL进行编译. 如果参数为字符串, 需要加上引号 '' . 参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降.
<!-- 安全的模糊查询写法 --><select id="selectUserByName" resultType="UserInfo"> SELECT * FROM user_info WHERE username LIKE CONCAT('%', #{name}, '%')</select>总结:在大多数情况下,应优先使用 #{} 以确保安全性;只有在需要动态指定表名、字段名或排序方式等特殊场景下,才考虑使用 ${},并确保参数来源可信。
在 MyBatis 中实现动态排序功能时,由于 #{} 会对参数进行预编译处理并添加引号,导致 SQL 语句语法错误,因此需要使用 ${} 来实现。
Mapper 接口方法定义:
// 根据指定字段排序查询用户列表List<UserInfo> selectUsersOrderBy(@Param("orderField") String orderField, @Param("orderType") String orderType);XML 映射配置:
<select id="selectUsersOrderBy" resultType="UserInfo"> SELECT * FROM user_info ORDER BY ${orderField} ${orderType}</select>测试方法:
@Testvoid testSelectUsersOrderBy() { // 按年龄降序排列 List<UserInfo> users = userInfoXMLMapper.selectUsersOrderBy("age", "DESC"); users.forEach(System.out::println);// 按创建时间升序排列List<UserInfo> users2 = userInfoXMLMapper.selectUsersOrderBy("create_time", "ASC");users2.forEach(System.out::println);}安全注意事项:
${} 时,必须确保参数值来自可信来源,避免用户直接输入在 MyBatis 中进行模糊查询时,虽然可以使用 ${},但由于安全考虑,推荐使用 #{} 配合 MySQL 的 CONCAT() 函数来实现。
Mapper 接口方法定义:
// 根据用户名模糊查询List<UserInfo> selectUsersLikeName(@Param("keyword") String keyword);XML 映射配置(安全写法):
<select id="selectUsersLikeName" resultType="UserInfo"> SELECT * FROM user_info WHERE username LIKE CONCAT('%', #{keyword}, '%')</select>XML 映射配置(不安全写法 - 不推荐):
<!-- 存在 SQL 注入风险,不推荐使用 --><select id="selectUsersLikeName" resultType="UserInfo"> SELECT * FROM user_info WHERE username LIKE '%${keyword}%'</select>测试方法:
@Testvoid testSelectUsersLikeName() { // 查询包含"张"字的用户 List<UserInfo> users = userInfoXMLMapper.selectUsersLikeName("张"); System.out.println("查询结果数量:" + users.size()); users.forEach(System.out::println);// 查询包含"admin"的用户List<UserInfo> users2 = userInfoXMLMapper.selectUsersLikeName("admin");System.out.println("查询结果数量:" + users2.size());}其他模糊查询模式:
LIKE CONCAT('%', #{keyword})LIKE CONCAT(#{keyword}, '%')LIKE CONCAT('%', #{keyword}, '%')(如上例)性能优化建议:
%,这会导致索引失效数据库连接池(Database Connection Pool)是一种用于管理数据库连接的技术,它在应用程序启动时创建一定数量的数据库连接,并将这些连接保存在一个"池"中。当应用程序需要访问数据库时,从连接池中获取一个空闲连接,使用完毕后归还给连接池,而不是直接关闭连接。
数据库连接池的主要优势:
- 提高性能:避免了频繁创建和销毁连接的开销
- 资源复用:连接可以被多个请求重复使用
- 连接管理:可以控制连接数量,防止数据库过载
- 超时处理:自动回收空闲连接,避免连接泄漏
常见的数据库连接池实现:

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.21</version></dependency>//如果SpringBoot版本为2.X, 使⽤druid-spring-boot-starter 依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version></dependency>
家人们,因为我前几天回了趟家,所以断更了几天,今天预计更新两篇哦。我这次回家,买的榴莲超级漂亮,而且家里水果店19.9一斤,武汉要25.9一斤,我应该从家里提一个过来的。失算了,现在有点痛苦,但是我找到实习了,所以我决定上两天班了再买,哈哈哈哈。