Mybatis:灵活掌控SQL艺术
在前面的文章中,小编分享了spring中相关的知识,但是没有分享到,如何去更高效操作数据库。
操作数据库传统的方法就是通过JDBC来进行操作。
这个传统方法使用上可谓是够麻烦的
1.首先创建一个数据源对象
2.设置该数据源的属性(密码、用户、位置……)
3.获取数据库的连接
3.构建sql语句、预编译sql语句、发送sql语句
这些操作,应用到开发中,是比较麻烦的,所以Mybatis就应运而生了。
Mybatis
这是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。
Mybatis避免几乎所有的JDBC代码和手动设置参数以及获取结果集的过程。
它最初是一个Apache软件基金会下发起的一个项目,最初名为ibatis,随着项目的演进,提供到的功能越来越多
ibatis团队决定从Apache软件基金会迁移出来,并改名为Mybatis。
持久层框架:持久层框架是一种用于简化数据库访问代码的软件工具,它位于应用程序的业务逻辑层和数据存储层之间。
Mybatis的核心功能:
SQL映射:Mybatis允许你通过XML或者注解的方式来编写SQL语句,并将其与Java方法进行映射。
对象关系映射:通过简单的配置,可以告诉Mybatis如何将数据库表的列映射到Java对象属性上
事务管理:Mybatis支持声明式事务管理,通常与Spring等框架集成使用,可以让你轻松管理事务,确保数据的一致性和完整性。
缓存机制:Mybatis提供了一级缓存和二级缓存的支持。一级缓存是SqlSession级别的缓存,默认开启且不能关闭,二级缓存则是跨SqlSession的缓存,需要手动配置启用。合理运用缓存,可以提高应用性能
动态SQL:Mybatis提供了强大的动态SQL功能,允许你根据不同的条件动态生成SQL语句。
那么接下来小编就来分享下Mybatis如何进行操作数据库的方式。
操作数据的方式,有两个方式,一是注解,二是XML。
注解:
准备工作:
引入依赖:
<!-- 这个依赖项用于将 MyBatis 集成到 Spring Boot 应用程序中。它简化了配置过程,并提供了自动配置功能,使得开发者可以更轻松地使用 MyBatis 进行数据库操作。此外,它还整合了 MyBatis 和 Spring 的事务管理、依赖注入等功能。-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<!--此依赖项是 MySQL 数据库的 JDBC 驱动程序,允许你的应用程序连接到 MySQL 数据库。<scope>runtime</scope> 表示该依赖仅在运行时需要,编译期间不需要。-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>非Springboot项目,比如像是Maven项目的话,那就这样引入:
<!--对于基本的 MyBatis 功能,你需要添加 MyBatis 的核心依赖:-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>最新版本号</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>最新版本号</version>
</dependency>数据准备:
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(127) NOT NULL,
`password` VARCHAR(127) NOT NULL,
`age` TINYINT(4) NOT NULL,
`gender` TINYINT(4) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
`phone` VARCHAR(15) DEFAULT NULL,
`delete_flag` TINYINT(4) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
--create_time字段:记录数据的创建时间(当前时间),仅在插入时生效。
--update_time字段:记录数据的最后更新时间,插入时默认值,更新时自动刷新(当前时间)
-- 添加用户信息
INSERT INTO mybatis_test.user_info(username, `password`, age, gender, phone)
VALUES ('admin', 'admin', 18, 1, '18612340001');
INSERT INTO mybatis_test.user_info(username, `password`, age, gender, phone)
VALUES ('zhangsan', 'zhangsan', 18, 1, '18612340002');
INSERT INTO mybatis_test.user_info(username, `password`, age, gender, phone)
VALUES ('lisi', 'lisi', 18, 1, '18612340003');
INSERT INTO mybatis_test.user_info(username, `password`, age, gender, phone)
VALUES ('wangwu', 'wangwu', 18, 1, '18612340004');这里,小编使用的是navicat客户端(文章最后会介绍)
sql语句执行效果如下:

配置application.yml文件
spring:
application:
name: MyBatis
#数据库配置
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: 自行填写
password: 自行填写(纯数字要加单引号)
driver-class-name: com.mysql.cj.jdbc.Driver
#为什么要配置打印日志,这里小编觉得原版打印出来不太好看,所以加上了这个打印日志配置
mybatis:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
准备工作到这里结束了,接下来进行代码编写
持久层代码编写
首先在创建好的项目中,创建一个mapper包和一个model包。
model包是存储实体类信息的,实体类是什么?
通常指的是软件开发中与数据表结构相对应的Java类。
既然我们表创建好了,那么代码中也要有对应的字段去映射
UserInfo(其他名字也可以):
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}在Mapper包,创建一个UserInfoMapper接口(其他名字也可以)。
@Mapper
public interface UserInfoMapper {
}为什么要使用Mapper注解呢?
这个Mapper注解是一个标记注解,它的作用是:
告诉 Spring 和 MyBatis:这个接口是一个 MyBatis Mapper。
Spring Boot 在启动时会扫描这些接口,并为它们生成动态代理对象(由 MyBatis 自动生成实现类)。
有了这个注解,你就可以直接通过
@Autowired注入这个接口并使用它进行数据库操作
动态代理简单解释下:
动态代理是Java中一种非常强大的机制,它允许你在运行时创建一个实现了一组接口的类的实例,而无需在编写的代码中显式地定义这些类。这个特性特别适用于像MyBatis这样的框架,它们需要在运行时根据配置或注解自动生成代码来处理数据库操作。
为什么要是一个接口呢?
这是因为Mybatis使用的是接口+动态代理的机制
这也是属于Mybatis的设计哲学之一:
开发者只定义接口方法(比如:
List<UserInfo> getAllUsers();)MyBatis 会在运行时自动生成该接口的实现类(动态代理)
实现类中封装了底层 JDBC 操作、SQL 执行、结果映射等逻辑
如若不想在之后的接口上,都写一Mapper注解,那么可以使用MapperScan注解
@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 自动扫描包下所有Mapper接口
public class MyBatiesApplication{
public static void main(String[] args) {
SpringApplication.run(MyBatiesApplication.class, args);
}
}接下来写我们的增删改查代码了。
查询:
通过Mybatis提供的Select注解
方式一:
//不推荐
@Select("select * from user_info")
List<UserInfo> selectUserInfo();
//返回的表中多行信息,所以用List接收不推荐原因原因:*号还要被自动解析为各个表字段,消耗性能
那么如何进行测试呢?
这里提供一个测试快捷方式
在UsetInfoMapper代码中右键



这里勾选下面selectUserInfo(),点击ok就自动生成测试类了
如若想手写测试,也是可以的,在该目录下创建一个测试类即可

那么一定义要加上@SpringBootTest,这样才会注入我们想要的UserInfoMapper对象
@SpringBootTest
class UserInfoMapperTest {
//要用到该方法,那么首先要注入对象,这是手写的。
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectUserInfo() {
System.out.println(userInfoMapper.selectUserInfo());
}
}此时在测试类中,点击该方法的左边的绿色三角形按钮即可允许
结果

方式二:
@Select("select id,username,password,age," +
"gender,phone,delete_flag,create_time,update_time from user_info")
List<UserInfo> selectUserInfo(); 生成测试:
@Test
void selectUserInfo2() {
userInfoMapper.selectUserInfo2().forEach(x-> System.out.println(x));
}结果:

此时,我们发现,为什么create_time字段没有值呢?
这是因为当用一个集合作为范围类型的的时候,此时,返回的结果会与UserInfo的属性进行一一映射
当发现字段不一样的的时候,就映射失败,默认为null了,基础数据类型就会默认为0。
为什么字段不能都合为一致呢?
这是因为java有java的开发规范,数据库有数据库的建表规范,这些规范形成一个行业共识,一般都不会去改的。
当然还是有办法的
方法三:
@Select("select id,username,password,age," +
"gender,phone,delete_flag as deleteFlag,create_time as createTime," +
"update_time as updateTime from user_info")
List<UserInfo> selectUserInfo();测试类一样的,就不再生成
结果:

方法四:
//注解实现
@Select("select id,username,password,age," +
"gender,phone,delete_flag,create_time,update_time from user_info")
@Results( {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> selectUserInfo2();@Results 注解用于定义结果映射(Result Mapping),它允许你更精细地控制如何将查询结果中的列映射到Java对象的属性。
生成测试:
@Test
void selectUserInfo2() {
userInfoMapper.selectUserInfo2().forEach(x-> System.out.println(x));
}结果是和方法三中是一致的,不再展示。
而该注解还可以进行复用
将Restults注解修改下:
@Results(id="baseMap",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})//复用注解
@Select("select id,username,password,age," +
"gender,phone,delete_flag,create_time,update_time from user_info")
@ResultMap("baseMap")
List<UserInfo> selectUserInfo3();运行该生成的测试代码后,结果与方法三一致
方法五:
通过配置文件修改
mybatis:
# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰自动转换此时呢,不用Restults注解也可以进行字段映射了
查询(带有条件):
上面刚刚使用的是不带条件查询,接下来介绍下带有条件查询的。
既然是带有条件,那么用到where或者order by等等,需要个参数。
如何写呢?
方法一:
//以下为非唯一主键查询,返回结果建议使用list
//该查询方式推荐使用
@Select("select * from user_info where gender=#{age} and age=#{gender}")
List<UserInfo> selectUserByGenderAndAge( Integer age,
Integer gender); 此时,where中的参数和方法中参数,要一一对应,特别是顺序上,使用#占位符,预编译时,对应参数用?代替
#{}占位符:
预编译 SQL 语句:使用
#{}时,MyBatis 会将传入的参数视为一个预编译 SQL 语句的参数。这意味着参数会被当作 JDBC 预编译语句中的参数来处理,可以有效地防止 SQL 注入攻击。类型安全:MyBatis 会自动根据 Java 类型对参数进行适当的转换。例如,如果你传递的是一个整数类型的参数,MyBatis 会确保它以正确的格式插入到 SQL 语句中。
测试前,修改下表中数据

生成测试:
@Test
void selectUserByGenderAndAge() {
System.out.println(userInfoMapper.selectUserByGenderAndAge(20, 0));
}结果:

值得一提的是,这样写,参数对应不上,会报出参数异常错误
//参数异常
@Select("select * from user_info where gender=#{age1} and age=#{gender}")方法二:
//参数排序,不推荐
@Select("select * from user_info where gender=#{param1} and age=#{param2}")
List<UserInfo> selectUserByGenderAndAge( Integer gender,
Integer age);方法中参数,gender和age,会被mybatis默认为param1为gender,age为param2.
所以这样写的话,可读性不是那么高
测试结果,与刚刚结果一致,不再展示
方法三:
//参数重命名,param中的参数要和@select中参数一致
@Select("select * from user_info where gender=#{gender} and age=#{age}")
List<UserInfo> selectUserByGenderAndAge(@Param("gender") Integer gender,
@Param("age")Integer age);测试结果,与刚刚结果一致,不再展示
增加:
通过Mybatis提供的Insert注解
这里可以通过方法,方法中,要求传入一个个参数,进行插入。
但这里,小编使用的是,通过参数中传递对象进行插入
当参数中是对象的时候,此时呢,mybatis会将对象中设置好的值,与插入语句中参数一一映射
//插入操作:(传递对象),返回影响行数
@Insert("insert into user_info (username,`password`,age,gender) " +
"values (#{username},#{password},#{age},#{gender})")
Integer insertUserInfo(UserInfo userInfo);values中,不需要userInfo.username相关操作,因为,这里没有涉及到重命名参数。
生成测试:
@Test
void insertUserInfo() {
UserInfo userInfo=new UserInfo();
userInfo.setUsername("李四");
userInfo.setPassword("123");
userInfo.setAge(18);
userInfo.setGender(0);
System.out.println(userInfoMapper.insertUserInfo(userInfo));
}结果展示:


有时候,我们插入一个数据的时候,需要返回它的ID值,作为参数去传递到其他接口,那么此时该如何做呢?
//同样传递对象,此时进行重命名,同时返回自增主键的值
@Options(useGeneratedKeys = true,keyProperty ="id")
@Insert("insert into user_info (username,`password`,age,gender)" +
" values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
Integer insertUserInfo2(@Param("userInfo") UserInfo userInfo);此时,使用了重命名后,values中的参数就要使用userInfo.username了。
生成测试:
@Test
void insertUserInfo2() {
UserInfo userInfo=new UserInfo();
userInfo.setUsername("王五");
userInfo.setPassword("456");
userInfo.setAge(20);
userInfo.setGender(0);
System.out.println(userInfoMapper.insertUserInfo2(userInfo)+"返回ID:"+
userInfo.getId());
}结果展示:


修改:
通过Mybatis提供的Update注解
//修改数据
@Update("update user_info set password=#{newPassword} where id=#{id}")
Integer updateUserInfo(String newPassword,Integer id);当然,方法传入的参数也可以是对象。
生成测试:
@Test
void updateUserInfo() {
System.out.println(userInfoMapper.updateUserInfo("12345",4));
}结果展示:


删除:
通过Mybatis提供的Delete注解
//删除数据
@Delete("delete from user_info where id=#{id}")
Integer deleteUserInfo(Integer id);生成测试
@Test
void deleteUserInfo() {
System.out.println(userInfoMapper.deleteUserInfo(5));
}结果展示:


排序:
@Select("select id,username,password,age,gender,phone,delete_flag,create_time,update_time" +
"from user_info order by id #{sort}")//(报错)
List<UserInfo> selectUserInfoByOrder(String sort); 生成测试:
@Test
void selectUserInfoByOrder() {
System.out.println(userInfoMapper.selectUserInfoByOrder("desc"));
}结果展示:

重要的一条:
Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''desc'' at line 1此时发现报错信息后,我们查看信息是从下往上看,找到你看得懂的地方。
可以发现,这是语法错误
这是因为使用#{}占位符的时候,当参数为String类型,此时,就会自动单引号,导致最终的结果会多一个单引号。
修改方法,使用${}占位符
@Select("select id,username,password,age,gender,phone,delete_flag,create_time,update_time " +
"from user_info order by age ${sort}")
List<UserInfo> selectUserInfoByOrder(String sort);测试代码一样的,这里就展示为结果

那么,也可以发现,使用$占位符的时候,就会直接将结果进行填入,不会使用?符号代替
like查询
//由于方法参数为String类型,会自动为填入结果增加单引号,所以这里就用$占位符
@Select("select id,username,password,age,gender,phone,delete_flag,create_time,update_time " +
"from user_info where username like '%${key}%'")
List<UserInfo> selectUserInfoByLike(String key); 测试之前,改下数据库信息

测试用例:
@Test
void selectUserInfoByLike() {
System.out.println(userInfoMapper.selectUserInfoByLike("java"));
}结果展示:

当然,除了这个方法,还可以使用更安全的:
使用数据库自带的contact拼接方法
@Select("select id,username,password,age,gender,phone,delete_flag,create_time,update_time " +
"from user_info where username like concat ('%',#{key},'%')")
List<UserInfo> selectUserInfoByLike(String key);测试用例和结果一样,就不做展示。
由刚刚的例子得知,使用#号占位符和$占位符也是可以进行参数替换的,那么它们有什么区别呢?
#{}占位符和${}区别
刚刚提到了SQL注入,那么SQL注入是什么呢?
SQL注入:
SQL注入(SQL Injection)是一种代码注入技术,攻击者通过将恶意的SQL代码插入到查询字符串中,进而操控数据库执行非授权的操作。
举例:
@Select("select * from user_info where username= '${username}' and password= '${password}'")
List<UserInfo> selectUserInfoByNameAndPassword(String username,String password);此时,当我们的${password}被用户输入用户和密码分别为为admin和 ' or 1='1后
此时呢,我们的SQL语句,就变成
select * from user_info where username='admin'and password= '' or 1='1';那么此时,后面or语句总是为真,所以就会把所有用户信息参数出来,这是很危险的
防止SQL注入简单建议: 核心建议:始终使用预编译语句和参数化查询
不要信任任何外部输入:所有来自用户的输入都应该被视为潜在的安全威胁,确保对这些输入进行验证和清理。
最小权限原则:为应用程序使用的数据库账户分配尽可能少的权限。例如,如果应用不需要删除表的功能,那么就不要给这个账户赋予删除权限。
保持软件更新:定期更新你的数据库管理系统、操作系统以及所使用的框架或库,以利用最新的安全补丁。
注解的方式就介绍到这里,接下来就介绍下XML的方式
XML
准备工作:
配置文件修改:
mybatis:
# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰自动转换文件创建:
在resources目录下创建个mapper目录,以及一个UserInfoMapper.xml(名字可以随心取),最后在启动类所在包的mapper包下,创建UserInfoXMLMapper接口(名字可以随心取)


在resources包下的xml文件中,填入以下基本内容
<!--这是 XML 文件的标准声明,表示该文件遵循 XML 1.0 规范,并且使用 UTF-8 编码格式。UTF-8 是一种字符编码方式,支持几乎所有的字符集,适合用于国际化的应用程序。 -->
<?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.nanxi.mybatis.mapper.UserInfoXmlMapper">
</mapper><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">:
简单解释下:
<!DOCTYPE mapper ... >:这是一个文档类型声明(Document Type Definition, DTD),它指定了当前 XML 文档遵循的规则。在这个例子中,mapper元素必须符合 MyBatis 提供的Mapper 3.0规范。"-//mybatis.org//DTD Mapper 3.0//EN":这是 DTD 的公共标识符,用来唯一标识该 DTD。"http://mybatis.org/dtd/mybatis-3-mapper.dtd":这是 DTD 的系统标识符,通常是一个 URL,指向实际的 DTD 文件位置。虽然现代开发环境中可能不需要直接访问这个 URL,但它有助于验证 XML 文件是否符合 MyBatis 的规范
namespace这个是说明,该XML下的SQL语句与哪个接口下的接口方法对应起来
插件下载(推荐):

该插件有助于我们定义好接口方法后,快速生成对应的SQL语句标签。
XML代码编写
XML中编写的SQL语句,是基于一对对SQL语句标签的,比如<select> </select>。
查询:
方法一:
<select id="selectUserInfo" resultType="com.nanxi.mybatis.model.UserInfo">
select id,username,password,age,gender,phone,
delete_flag as deleteFlage,create_time as createTime,update_time as updateTime
from user_info;
</select>resultType="com.nanxi.mybatis.model.UserInfo":
这个代表的是,返回类型是哪种的。
接口类中:
@Mapper
public interface UserInfoXmlMapper {
List<UserInfo> selectUserInfo();
}生成测试:
@SpringBootTest
class UserInfoXmlMapperTest {
@Autowired
private UserInfoXmlMapper mapper;
@Test
void selectUserInfo() {
mapper.selectUserInfo().forEach(x-> System.out.println(x));
}结果展示:

不想写AS去规范变量名的话,可以使用到resultMap标签
方法二:
<resultMap id="BaseMap" type="com.nanxi.mybatis.model.UserInfo">
<!-- 表中id涉及到主键,强烈建议写上id标签-->
<id property="id" column="id"></id>
<result column="delte_flage" property="deleteFlag"></result>
<result column="update_time" property="updateTime"></result>
<result column="create_time" property="createTime"></result>
</resultMap>
<select id="selectUserInfo" resultMap="BaseMap">
select id,username,password,age,gender,phone,delete_flag,create_time,update_time
from user_info;
</select> 测试、接口以及结果都一样,这里就不做展示
以上这个是一种复用写法,如若想对单个查询标签起效
可以把resultMap中的id和type去掉,以及select标签下的resultMap去掉即可。
除了这个方法,还有一个就是修改配置文件,这个与注解那里一模一样,就不做讲解。
增加:
方法一:
<!-- 插入操作-->
<insert id="insertUserInfo">
insert into user_info(username,password,age,gender)
values(#{username},#{password},#{age},#{gender})
</insert>接口方法:
Integer insertUserInfo(UserInfo userInfo);生成测试:
void insertUserInfo() {
UserInfo userInfo=new UserInfo();
userInfo.setUsername("java");
userInfo.setPassword("java112");
userInfo.setAge(18);
userInfo.setGender(0);
System.out.println(mapper.insertUserInfo(userInfo));
}结果展示:


方法二:
<!-- 插入操作(重命名方式)-->
<insert id="insertUserInfo2">
insert into user_info(username,password,age,gender)
values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})
</insert>接口方法:
Integer insertUserInfo2(@Param("userInfo") UserInfo userInfo);生成测试内容与结果,与方法一类似,所以就不做展示。
更新:
<!-- 更新操作-->
<update id="updateUserInfo">
update user_info set `password`=#{password} where id=#{id};
</update>接口方法:
//(也可以通过对象)进行传入
Integer updateUserInfo(@Param("password") String password,
@Param("id") Integer id);生成测试:
@Test
void updateUserInfo() {
System.out.println(mapper.updateUserInfo("123wangwu", 4));
}结果展示:


删除:
<!-- 删除操作-->
<delete id="deleteUserInfo">
delete from user_info where id=#{deleteId};
</delete>接口方法
Integer deleteUserInfo(@Param("deleteId") Integer id);生成测试:
@Test
void deleteUserInfo() {
System.out.println(mapper.deleteUserInfo(5));
}结果展示


对于XML中的一些基础增删查改就介绍到这。
接下来介绍下动态SQL语句。
动态SQL语句
举个例子
比如你进行用户信息编辑的时候,页面让你填入电话号码,但这个电话号码不一定是必选项,此时呢,你填入了电话号码,编辑后信息就有电话号码,如若你没有填入,编辑后信息就没有电话号码。
如何去实现呢?
接下来用到了if标签了
if标签:
<!-- 插入选项是可选的-->
<insert id="insertUserInfoByCondition">
insert into user_info (username,
`password`,
age,
<if test="phone!=null">
phone,
</if>
gender
)
values (
#{username},
#{password},
#{age},
<if test="phone!=null">
#{phone},
</if>
#{phone}
)
</insert>接口方法:
Integer insertUserInfoByCondition(UserInfo userInfo);生成测试:
@Test
void insertUserInfoByCondition() {
UserInfo userInfo=new UserInfo();
userInfo.setUsername("c++112");
userInfo.setPassword("c++1234");
userInfo.setAge(20);
userInfo.setGender(0);
// userInfo.setPhone("134879924");
System.out.println(mapper.insertUserInfoByCondition(userInfo));
}结果展示:


把setPhone的注释去掉后:
结果:

那么这个if标签要谨慎使用
比如
<insert id="insertUserInfoByCondition">
insert into user_info (username,
`password`,
age,
<if test="phone!=null">
phone,
</if>
<if test="gender!=null">
gender
</if>
)
values (
#{username},
#{password},
#{age},
<if test="phone!=null">
#{phone},
</if>
<if test="gender!=null">
#{gender}
</if>
)
</insert>当phone和gender都没有填入,此时呢,sql语句这里,到age就结束,但是多了逗号没有去处理,那么就会出现
语法错误
那么接下来用到了trim标签了
trim标签:
它有这几个属性值:

suffixOverrides:去除后缀
suffix:添加 后缀
prefix:添加前缀
prefixOverrides:去除前缀
这些属性根据情况进行选择即可
这里小编当if语句不生效时,前一个的字段后逗号会去掉,使用到了去除后缀属性
代码:
<!-- 分割标签-->
<insert id="insertUserInfoByTrim">
insert into user_info (
<trim suffixOverrides=",">
<if test="username!=null">
username,
</if>
<if test="password!=null">
`password`,
</if>
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender,
</if>
<if test="phone!=null">
phone
</if>
</trim>
)
values (
<trim suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender},
</if>
<if test="phone!=null">
#{phone}
</if>
</trim>
)
</insert>接口方法:
Integer insertUserInfoByTrim(UserInfo userInfo);生成测试:
@Test
void insertUserInfoByTrim() {
UserInfo userInfo=new UserInfo();
userInfo.setUsername("JS112");
userInfo.setPassword("Js12345");
userInfo.setAge(20);
userInfo.setGender(0);
// userInfo.setPhone("13712661733");
System.out.println(mapper.insertUserInfoByTrim(userInfo));
}结果:


where标签:
它存在以下特性:
自动添加 WHERE 关键字:如果
<where>标签内的内容非空,则会自动添加WHERE关键字。这意味着你不需要手动在 SQL 中写WHERE,这可以减少一些潜在的错误。去除不必要的 AND 或 OR:当多个条件组合时,可能会出现第一个条件前有
AND或OR的情况。<where>标签会自动移除这些多余的关键词,确保生成的 SQL 语句正确无误。支持动态条件:可以在
<where>标签内部使用其他动态 SQL 元素(如<if>、<choose>等),根据不同的条件构建灵活的查询条件。
代码:
<!-- where标签-->
<select id="selectUserInfoByWhere" resultType="com.nanxi.mybatis.model.UserInfo">
select id,username,password,age,gender,phone,delete_flag,create_time,update_time
from user_info
<where>
<if test="age!=null">
age=#{age}
</if>
<if test="gender!=null">
and gender=#{gender}
</if>
<if test="password!=null">
and password=#{password}
</if>
</where>
</select>接口方法:
List<UserInfo> selectUserInfoByWhere(UserInfo userInfo);生成测试:
@Test
void selectUserInfoByWhere() {
UserInfo userInfo=new UserInfo();
userInfo.setAge(18);
userInfo.setGender(0);
System.out.println(mapper.selectUserInfoByWhere(userInfo));
}结果:

set标签:
它存在以下特性:
自动添加 SET 关键字:如果
<set>标签内的内容非空,则会自动添加SET关键字。去除多余的逗号:当多个条件组合时,可能会出现最后一个字段前有多余逗号的情况。
<set>标签会自动移除这些多余的逗号,确保生成的 SQL 语句正确无误。支持动态更新字段:可以在
<set>标签内部使用其他动态 SQL 元素(如<if>、<choose>等),根据不同的条件构建灵活的更新操作。
代码:
<!-- set标签-->
<update id="updateUserInfoBySet">
update user_info set
<if test="password!=null">
`password` = #{password},
</if>
<if test="age!=null">
age=#{age},
</if>
<if test="phone!=null">
phone=#{phone}
</if>
where id=#{id};
</update>接口方法:
Integer updateUserInfoBySet(UserInfo userInfo);测试代码:
@Test
void updateUserInfoBySet() {
UserInfo userInfo=new UserInfo();
userInfo.setId(11);
userInfo.setPhone("1242533");
System.out.println(mapper.updateUserInfoBySet(userInfo));
}结果:


当我们想进行批量删除的时候,可以使用到foreach标签:
foreach标签
<!-- foreach标签-->
<delete id="deleteUserInfoByDelete">
delete from user_info where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>collection参数是要和传入的集合参数名一致
item参数是代表中,从ids集合取出来的元素用id去接收
separator是代表着分隔符,是逗号
open和close代表着,从那里开始从哪里结束
接口方法:
Integer deleteUserInfoByDelete(List<Integer> ids);生成测试:
@Test
void deleteUserInfoByDelete() {
List<Integer> list = List.of(10, 11, 12);
System.out.println(mapper.deleteUserInfoByDelete(list));
}结果:


include标签
当我们发现,对于XML编写中,一些语句是重复出现了,所以整体看起来比较繁重。
使用include标签,可以把它合在一起,然后增删查改标签页也可以使用了
代码:
<!-- include标签-->
<sql id="allColumn">
id,username,password,age,gender,phone,delete_flag,create_time,update_time
</sql>
<select id="selectUserInfo" resultMap="BaseMap">
select
<include refid="allColumn"></include>
from user_info;
</select>结果与之前展示过,这里就不再展示。
注解使用动态SQL语句
对于注解来说,就不可以使用动态SQL语句吗?
当然不是,只是会显示稍微麻烦,可读性没那么好罢了。
这里举个例子:
@Delete({
"<script>",
"DELETE FROM users WHERE id IN",
"<foreach item='id' collection='list' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
"</script>"
})
Integer deleteUsersByIds(@Param("list") List<Integer> ids);测试和结果就不做展示,与foreach标签那里,大差不差。
所以注解使用动态SQL语句,只需把<script> </script>标签即可
所以总的来说,这个mybatis可是方便了广大程序员,毕竟少些了很多共性代码。
既然这样的话,mybatis何不再努力下,把一些增删改查的基本代码一并帮写了?
接下来,介绍这个一个mybatis的增强工具
Mybatis Generator
是一个用于生成 MyBatis 相关代码的工具,旨在减少开发人员的手动编码工作量。它能够自动生成与数据库表对应的实体类(JavaBeans)、Mapper接口以及相应的XML映射文件.
快速使用:
1.引入插件:
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>deploy</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!--generator配置文件所在位置-->
<configurationFile>src/main/resources/generator/generator.xml</configurationFile>
<!-- 允许覆盖生成的文件, mapxml不会覆盖, 采用追加的方式-->
<overwrite>true</overwrite>
<verbose>true</verbose>
<!--将当前pom的依赖项添加到生成器的类路径中-->
<includeCompileDependencies>true</includeCompileDependencies>
</configuration>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</plugin>2.解释相关信息:
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
这部分表示引入了 MyBatis Generator 的 Maven 插件。
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>deploy</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions><executions>:定义插件的执行方式和时机。<execution>:一个具体的执行配置项。<id>:执行 ID,用于唯一标识这个 execution,便于调试和日志查看。<phase>:绑定到 Maven 生命周期中的哪个阶段,默认是none,这里设置为deploy阶段执行 MBG。<goals>:要执行的目标(即插件的功能),这里调用的是generate,表示运行 MyBatis Generator 生成代码。
添加配置文件:
值得注意的是,配置文件要和pom.xml文件中,指定的一样

配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<!-- 一个数据库一个context -->
<context id="MysqlTables" targetRuntime="MyBatis3" defaultModelType="flat">
<!--去除注释-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库链接信息-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis_test?serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true"
userId="root"
password='12345'>
</jdbcConnection>
<!-- 生成实体类 -->
<javaModelGenerator targetPackage="com.nanxi.mybatis.generator" targetProject="src/main/java" >
<property name="enableSubPackages" value="false"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成mapxml文件 -->
<sqlMapGenerator targetPackage="generator" targetProject="src/main/resources" >
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成mapxml对应client,也就是接口dao -->
<javaClientGenerator targetPackage="com.nanxi.mybatis.generator" targetProject="src/main/java" type="XMLMAPPER" >
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- table可以有多个,每个数据库中的表都可以写一个table,tableName表示要匹配的数据库表,也可以在tableName属性中通过使用%通配符来匹配所有数据库表,只有匹配的表才会自动生成文件 -->
<table tableName="user_info">
<property name="useActualColumnNames" value="false" />
<!-- 数据库表主键 -->
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table>
</context>
</generatorConfiguration>3.对配置文件相关信息解释:
<context id="MysqlTables" targetRuntime="MyBatis3" defaultModelType="flat">targetRuntime:指定目标运行时框架版本。
常用值:
MyBatis3:适用于最新版 MyBatis。MyBatis3Simple:不生成 Example 查询方法。
defaultModelType:控制模型类(实体类)的生成方式。
常见值:
flat:为每张表只生成一个实体类(推荐使用)。hierarchical:按层次结构生成多个类(如主键类、查询条件类等
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true" />
</commentGenerator>常见 property:
suppressDate:设置为
true表示在注释中不显示生成时间。默认会加上生成时间,例如:
* @generated Thu May 27 09:45:00 CST 2025
suppressAllComments:设置为
true表示完全禁用所有注释(包括警告信息)。如果你不希望看到“不要修改此文件”的警告注释,可以开启这个。
<javaModelGenerator targetPackage="com.nanxi.mybatis.generator" targetProject="src/main/java" >
<property name="enableSubPackages" value="false"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>属性解释:
targetPackage:生成的 Java 类所在的包名。targetProject:生成的 Java 类所在项目的路径(相对于项目根目录)。enableSubPackages:是否根据表名自动创建子包(如 user -> com.example.model.user)。
设置为
false表示不自动创建子包。
trimStrings:设置为
true表示对字符串类型的字段进行 trim 处理(防止空格问题)
<sqlMapGenerator targetPackage="generator" targetProject="src/main/resources" >
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>属性解释:
targetPackage:XML 文件的包路径(通常是资源目录下的子路径)。targetProject:XML 文件输出位置。enableSubPackages:是否根据表名自动生成子包。
设置为
false表示统一放在指定目录下。
<javaClientGenerator targetPackage="com.nanxi.mybatis.generator" targetProject="src/main/java" type="XMLMAPPER" >
<property name="enableSubPackages" value="false" />
</javaClientGenerator>属性解释:
targetPackage:接口类所在的包名。targetProject:接口类输出路径。type:接口类型,常用值:
ANNOTATEDMAPPER:基于注解的 Mapper(不需要 XML)。MIXEDMAPPER:混合模式(有注解也有 XML)。XMLMAPPER:纯 XML 模式(最常用)。
enableSubPackages:是否根据表名生成子包。
<table tableName="user_info">
<property name="useActualColumnNames" value="false" />
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table>属性解释:
tableName:要生成代码的数据库表名,支持通配符%,比如"%"表示所有表。useActualColumnNames:设置为
false表示列名转换为驼峰命名法(如user_name→userName)。设置为
true表示保留实际列名(不建议)。
<generatedKey>:用于处理主键自增字段。
属性解释:
column="id":主键字段名。sqlStatement="Mysql":数据库类型(MySQL)。identity="true":表示是自增主键,插入数据后会回填 ID。
如若遇到generator.xml中,dtd信息报错:
可以对它进行临时忽略:
点击idea->file->setting->Languages & FrameWorks->Schemas and DTDS

点击下面的Ignored上的+号
出现的页面中,加入xml中,对应出错的URL即可

4.运行使用:

如若想生成动态SQL,按照以下步骤:
pom文件引入该依赖:
<dependency>
<groupId>org.mybatis.dynamic-sql</groupId>
<artifactId>mybatis-dynamic-sql</artifactId>
<version>1.3.1</version> <!-- 或者更高版本,比如1.4.0+ -->
</dependency>generator.xml修改:
<context id="MysqlTables" targetRuntime="MyBatis3DynamicSql" defaultModelType="flat">注意,版本要相互对应,提供官方的链接,可以自行去比对
Introduction to MyBatis Generator
最后点击运行即可。
这个增强工具,更多是在项目初期的时候使用,一键生成代码,减少工作量。
介绍Navicat
Navicat 是一款功能强大且广受欢迎的数据库管理和开发工具,支持多种数据库系统。它由 PremiumSoft CyberTech Ltd. 开发,并面向数据库管理员、软件开发者等专业人士设计,旨在简化数据库管理流程,提高工作效率。
下载软件:
下载这个免费轻量版

下载安装。
安装成功后,进入该页面:

点击连接:

点击MySQL,小编本地是MySQL,所以,这个具体数据,看个人电脑是什么,或者说远程的SQL是什么
进入该页面

输入对应内容连接即可
连接成功后:

右键demo可以创建数据库,点击数据库下的表,右键它可以创建表。
如若想输入SQL语句来完成,那么点击对应最上面的新建查询,打开后,输入对应的SQL语句。
到这里,mybatis一些基础使用,小编分享到这。