目录
2.1.1 参数占位符 #{ } 和 ${ }(常见面试问题)
从上边的事例可以看出 ${ } 可以实现的功能 #{ } 都能实现,并且 ${ } 还有 SQL 注入的问题,那么为什么 ${ } 的写法还存在?
2.2 类中的属性和数据库表中的字段名不一致时,那么查询结果为 null,解决方案
2.2.1 将类中的属性和表中的字段名保持一致(最简单的解决方案)
2.2.2 使用 sql 语句中的 as 进行列名(字段名)重命名,让列名(字段名)等于属性名
MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及高级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
MyBatis 是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库工具
MyBatis 学习只分为两部分:
- 配置 MyBatis 开发环境
- 使用 MyBatis 模式和语法操作数据库
1.MyBatis 框架的搭建
1.1 创建数据库和表
-- 创建数据库
drop database if exists mycnblog2023;
create database mycnblog2023 DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog2023;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog2023`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
1.2 添加 MyBatis 依赖
新创建的 Mybatis 启动时报错是正常的,因为未设置要连接的具体 MySQL 的详细信息
1.3 设置 MyBatis 配置
1.3.1 设置数据库的连接信息
application.properties:
#设置数据库的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog2023?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
1.3.2 设置 XML 保存路径和命名格式
MyBatis 的 XML 中保存是查询数据库的具体操作 SQL:
# 设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
1.4 根据 MyBatis 写法完成数据库得操作
常规的写法,包含了两个文件:
- 接口:方法的声明(给其他层(service)调用)
- XML:实现接口
添加实体类:
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
//添加实体类
@Data
public class Userinfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
1.4.1 定义接口
使用 @Mapper 注解,即数据持久层标志,相当于五大类注解中的 @Repository
package com.example.demo.dao;
import com.example.demo.model.Userinfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper //数据持久层的标志
public interface UserMapper {
List<Userinfo> getAll();
}
1.4.2 使用 XML 实现接口
MyBatis 固定的 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.example.demo.dao.UserMapper">
</mapper>
注意:namespace 是表明当前 xml 实现的是哪个接口的(不是使用 xml 文件名和接口进行匹配的)
UserMapper.xml 查询所有⽤户的具体实现 SQL:
<?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.example.demo.dao.UserMapper">
<select id="getAll" resultType="com.example.demo.model.Userinfo">
select * from userinfo
</select>
</mapper>
其中:
- <mapper>标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接⼝的全限定名,包括全包名.类名。
- id:方法名(是和 Interface(接⼝)中定义的方法名称⼀样的),表示对接⼝的具体实现方法
- id 中需要实现一个返回类型 resultType:是返回的数据类型,也就是开头我们定义的实体类
- 写具体的 SQL 时,注意没有分号
使用单元测试验证是否生效:在需要生成单元测试类(UserMapper)中右键 generate,生成一个 Test
package com.example.demo.dao;
import com.example.demo.model.Userinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
//Spring 容器就是 IoC 容器,实现 DI,就可以把测试类(UserMapper)注入进去,使用 @Autowired 注解
@Autowired
private UserMapper userMapper;
@Test
void getAll() {
List<Userinfo> list = userMapper.getAll();
System.out.println(list);
}
}
其中:
- 需要添加一个 @SpringBootTest 注解:告诉当前的测试程序,目前项目时运行在 Spring Boot 容器中
- Spring 容器就是 IoC 容器,实现 DI,就可以把测试类(UserMapper)注入进去,使用 @Autowired 注解
mysql 中:
运行测试类,实现功能:
在这里,我介绍一种 MyBatis 插件:为了方便开发 MyBatis 实现 XML 和对应的接口之技安的快速跳转,可以安装一个 MyBatisX 插件
实现快速跳转:
2.MyBatis查询操作
2.1 单表查询
根据 id 查询一条信息
UserMapper 类:
@Mapper //数据持久层的标志
public interface UserMapper {
//传递单个参数
Userinfo getUserById(@Param("id") Integer id);
}
- 传递参数需要使用包装类,不能使用基础类型(包装类可以接收 null,前端没有传递值不会报错;而基础类型会报错——500)
- 传递非定义业务对象需要使用注解 @Param :给参数命名,比如在 mapper 里面某方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的 id 值,只需要取它的参数名 userId 就可以了。
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.example.demo.dao.UserMapper">
<select id="getUserById" resultType="com.example.demo.model.Userinfo">
select * from userinfo where id = ${id}
</select>
</mapper>
测试类:
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
@Test
void getUserById() {
Userinfo userinfo = userMapper.getUserById(1);
System.out.println(userinfo.toString());
}
}
传参有两种方式:1️⃣使用 ${ 参数名 }(使用 @value 注解读取配置文件的写法)
执行语句:
2️⃣使用 #{ 参数名 }
<?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.example.demo.dao.UserMapper">
<select id="getUserById" resultType="com.example.demo.model.Userinfo">
select * from userinfo where id = #{id}
</select>
</mapper>
- 使用 jdbc 设置的 ?,? 是一个占位符,意味着 SQL 执行的时候将是预编译处理
2.1.1 参数占位符 #{ } 和 ${ }(常见面试问题)
- #{ }:预编译处理
- ${ }:字符直接替换
- 预编译处理:MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 号,使用 PreparedStatement 的 set 方法来赋值
- 直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值
预编译和直接替换区别:
- 预编译:执行是安全的,可以防止 SQL 注入
- 直接替换:执行是不安全的
SQL 注入:使用特殊的 SQL 语句完成非法操作
例如,进行用户名和密码的判断:
假设这是一个登陆的功能 ,必须要求用户名和密码都输入正确,才能返回一条 SQL 语句;
则 SQL 注入指的是密码输入错误也能查到数据
UserMapper 类:
@Mapper //数据持久层的标志
public interface UserMapper {
//登陆功能
Userinfo login(@Param("username") String username, @Param("password") String password);
}
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.example.demo.dao.UserMapper">
<select id="login" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username = '${username}' and password = '${password}'
</select>
</mapper>
测试类:
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
@Test
void login() {
String username = "admin";
String password = "' or 1='1";
Userinfo userinfo = userMapper.login(username, password);
System.out.println(userinfo.toString());
}
}
在这里还有一个区别:
- #{ } :使用在非数值类型或数值类型下是完全可以的
- ${ }:非数值类型需要加 ' ' ,否则会报错
运行测试:
我们发现竟然可以查询出数据:输入不是正确密码,竟然能查询处结果(这就是最简单的 SQL 注入);则 使用 ${ } ,那么可以通过 ' or 1='1 这个命令登陆各种系统—— SQL 注入
使用 #{ }:
<?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.example.demo.dao.UserMapper">
<select id="login" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username = #{username} and password = #{password}
</select>
</mapper>
MyBatis 在处理 #{ } 时,会将 SQL 中的 #{ } 替换为 ? 号,使用 PreparedStatement 的 set 方法来赋值
从上边的事例可以看出 ${ } 可以实现的功能 #{ } 都能实现,并且 ${ } 还有 SQL 注入的问题,那么为什么 ${ } 的写法还存在?
2.1.2 ${ } 的优点
例如,SQL 语句:
select * from userinfo order by id asc;
select * from userinfo order by id desc;
- 在这种情况下,使用 ${ } 直接替换就行
- 使用 #{ },asc、desc 就是一个占位符,并且它是一个 String,# 就会加单引号,即 'desc'、'asc',这是不可取的
- ${ } 适用场景:当业务需要传递 SQL 命令,只能使用 ${ },不能使用 #{ }
- ${ } 注意事项:如果要使用 ${ },那么传递的参数一定要能被穷举,否则不能使用(上述只能是 desc 或者 asc)
2.2 类中的属性和数据库表中的字段名不一致时,那么查询结果为 null,解决方案
已知数据库:
类中属性(Userinfo 类):
//添加实体类
@Data
public class Userinfo {
private int id;
private String name;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
@Test
void getAllByOrder() {
List<Userinfo> list = userMapper.getAllByOrder("desc");
System.out.println(list);
}
UserMapper 类:
List<Userinfo> getAllByOrder(@Param("myOrder") String myOrder);
XML 配置:
<select id="getAllByOrder" resultType="com.example.demo.model.Userinfo">
select * from userinfo order by id ${myOrder}
</select>
2.2.1 将类中的属性和表中的字段名保持一致(最简单的解决方案)
存在的问题: 定义的 name 已经在多个类中使用,那么把定义的 name 改为 username,随之其他类也需要改动,工作量有可能会增大
2.2.2 使用 sql 语句中的 as 进行列名(字段名)重命名,让列名(字段名)等于属性名
xml 配置修改 sql 语句:
<select id="getAllByOrder" resultType="com.example.demo.model.Userinfo">
select id, username as name, password from userinfo order by id ${myOrder}
</select>
2.2.3 返回字典映射:resultMap
- 字段名和程序中的属性名不同的情况,可使用 resultMap 配置映射
- 一对一和一对多关系可以使用 resultMap 映射并查询数据
定义一个 resultMap,将属性名和字段名进行手动映射
UserMap.xml:
<resultMap id="BaseMap" type="com.example.demo.model.Userinfo">
<id column="id" property="id"></id>
<result column="username" property="name"></result>
<result column="password" property="password"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
id 设置到查询结果中:
<select id="getAllByOrder" resultMap="BaseMap">
select * from userinfo order by id ${myOrder}
</select>
2.3 like 查询操作
UserMapper 类:
//like 查询
List<Userinfo> getLikeList(@Param("username") String username);
UserMapper.xml:
<!--like 查询-->
<select id="getLikeList" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username like '%#{username}%'
</select>
生成单元测试:
@Test
void getLikeList() {
String username = "三";
List<Userinfo> list = userMapper.getLikeList(username);
System.out.println(list);
}
- 这个时候发现使用 like 的时候报错了,因为 #{username} 是预执行,会变成 ?,则在设置 问号 的时候会加单引号 ,相当于:select * from userinfo where username like '%'username'%';
- 换成 ${ } 可以吗?可以,但是不能去用;因为使用 ${ }的场景:数值或者能被穷举或 过滤(风险也很大,关键东西多,过滤不彻底)
2.3.1 解决方案一:去掉单引号
<!--like 查询-->
<select id="getLikeList" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username like #{username}
</select>
String username = "%三%";
这个时候就不存在单引号中在嵌套一个单引号
2.3.2 解决方案二:mysql 内置函数 concat
例如:使用 concat 进行拼接
UserMapper.xml:
<!--like 查询-->
<select id="getLikeList" resultType="com.example.demo.model.Userinfo">
select * from userinfo where username like concat('%', #{username}, '%')
</select>
单元测试:
String username = "三";
3. 增、删、改操作
- <insert> 标签:插入语句
- <update> 标签:修改语句
- <delete> 标签:删除语句
3.1 删除操作 + @Transactional 注解
在 interface 中定义一个方法接口:
@Mapper //数据持久层的标志
public interface UserMapper {
//删除操作
int delById(@Param("id") Integer id);
}
Mapper.xml 实现标签:
<delete id="delById">
delete from userinfo where id = #{id}
</delete>
删除操作不需要返回类型,可以不写 resultType
已知数据库:
生成测试类:
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
@Transactional //事务,单元测试添加 @Transactional,执行完成之后会进行回滚操作:既能实现测试功能,同时不会污染或影响数据库
//已经构建了数据库,不能说测试的时候删除就没了
@Test
void delById() {
int id = 2;
int result = userMapper.delById(id);
System.out.println("受影响的行数:" + result);
}
}
添加一个注解 @Transactional,这是一个事务注解,执行完成之后会进行回滚操作,这样既能实现测试功能,同时不会污染或影响数据库(已经构建看数据库,不能说测试的时候删除就没了)
这个时候我们执行数据库操作发现这条语句依然存在:
没有 注解 @Transactional
- 如果测试功能,并且不想影响数据库,那么就添加一个 @Transactional 注解(适合所有场景,不单单是删除操作)
3.2 修改用户操作
UserMapper 类:
//修改操作
int update(Userinfo userinfo);
传递对象不需要用 @Param
UserMapper.xml(修改操作也不需要返回类型,不写 resultType):
<update id="update">
update userinfo set username = #{username} where id = #{id}
</update>
传递对象中的#{ }:括号中直接使用对象的属性名,不需要写 userinfo.username;Mybatis 框架已经默认做了这个过程
生成单元测试:
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
@Test
void update() {
Userinfo userinfo = new Userinfo();
userinfo.setId(1);
userinfo.setUsername("超级管理员");
int result = userMapper.update(userinfo);
System.out.println("受影响的行数:" + result);
}
}
数据库:
当然如果不想影响数据库,和删除操作一样添加 @Transactional 注解
3.3 添加用户操作
3.3.1 MyBatis 添加操作,返回受影响的行数
UserMapper 类:
//修改操作
int update(Userinfo userinfo);
UserMapper.xml:
<insert id="add">
insert into userinfo(username, password, photo) values(#{username}, #{password}, #{photo})
</insert>
同理——传递对象中的#{ }:括号中直接使用对象的属性名,不需要写 userinfo.username;Mybatis 框架已经默认做了这个过程
生成单元测试:
//添加操作
@Test
void add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("张三");
userinfo.setPassword("123");
userinfo.setPhoto("/image/default.png");
int result = userMapper.add(userinfo);
System.out.println("受影响的行数:" + result);
}
数据库:
这个时候发现 id 好像不是很符合,接下来我们来设置 自增id
3.3.2 返回自增 ID
UserMapper 类:
//返回自增 ID
int insert(Userinfo userinfo);
UserMapper.xml:
<!--返回自增 id-->
<insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into userinfo(username, password, photo) values(#{username}, #{password}, #{photo})
</insert>
- useGeneratedKeys:这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键,默认值:false
- keyColum:数据库自增主键字段名(列名)
- keyProoerty:指定能够唯⼀识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值
生成单元测试:
@Test
void insert() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("李四");
userinfo.setPassword("123456");
userinfo.setPhoto(" ");
int result = userMapper.insert(userinfo);
System.out.println("受影响的行数:" + result + " | ID:" + userinfo.getId());
}
4. 多表查询
4.1 一对一映射:使用注解
添加实体类:
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Articleinfo {
private int id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int uid;
private int rcount;
private int state;
//联表字段
private String username;
}
注解实现(查询文章的所有信息和用户的username),在这里使用 @Select 注解写 sql 相当于 xml 中的 sql:
package com.example.demo.dao;
import com.example.demo.model.Articleinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ArticleMapper {
//查询注解
//查询文章所有信息和用户的username
@Select("select a.*,u.username from articleinfo a left join userinfo u on a.uid=u.id")
List<Articleinfo> getAll();
}
生成测试类:
package com.example.demo.dao;
import com.example.demo.model.Articleinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void getAll() {
List<Articleinfo> list = articleMapper.getAll();
System.out.println(list);
}
}
4.2 一对多映射:文章案例
文章案例:查询一个用户的多篇文章
把查询分成两个查询:在业务类中首先查询到用户信息,再用用户的 id 去文章表里查询文章的 list,把 list 设置到文章用户的文章列表中
面试问项目有没有使用到多线程?上边案例就是如此,启动线程池,同时查询两张表(一张用户表,一张文章表),查询完成之后拼接实现查询
查询用户多篇文章,实体类 Userinfo 中添加字段:
@Data
public class Userinfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
private List<Articleinfo> alist;
}
UserMapper 类:
@Select("select * from userinfo where id=#{id}")
Userinfo getUserById2(@Param("id") Integer id);
ArticleMapper 类:
@Select("select * from articleinfo where uid=#{uid}")
List<Articleinfo> getListByUid(@Param("uid") Integer uid);
单元测试(单线程实现):
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
//Spring 容器就是 IoC 容器,实现 DI,就可以把测试类(UserMapper)注入进去,使用 @Autowired 注解
@Autowired
private UserMapper userMapper;
@Autowired
private ArticleMapper articleMapper;
@Test
void getUserList() {
int uid = 1;
//1.根据 uid 查询 userinfo
Userinfo userinfo = userMapper.getUserById2(uid);
System.out.println(userinfo);
//2.根据 uid 查询文章列表
List<Articleinfo> list = articleMapper.getListByUid(uid);
//组装数据
userinfo.setAlist(list);
System.out.println(userinfo);
}
}
多线程:
@SpringBootTest //告诉当前的测试程序,目前项目时运行在 Spring Boot容器中
class UserMapperTest {
@Test
void getUserList() {
int uid = 1;
//定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100));
final Object[] resultArray = new Object[2];
threadPool.submit(new Runnable() {
@Override
public void run() {
//1.根据 uid 查询 userinfo
resultArray[0] = userMapper.getUserById2(uid);
}
});
threadPool.submit(new Runnable() {
@Override
public void run() {
//2.根据 uid 查询文章列表
resultArray[1] = articleMapper.getListByUid(uid);
}
});
//组装数据(等线程池执行完成之后)
while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
}
Userinfo userinfo = (Userinfo) resultArray[0];
userinfo.setAlist((List<Articleinfo>) resultArray[1]);
System.out.println(userinfo);
}
}
5.动态 SQL
5.1 if 标签
语法:
<if test=" ">
....
</if>
- 使用场景 :在注册用户的时候,有可能分为两种注册信息,必填字段和非必填字段,如果在添加用户的时候不确定的字段传入,这时候就需要使用动态标签 <if> 来判断
<!--动态添加操作-->
<insert id="add2">
insert into userinfo(username, password
<if test="photo != null">
,photo
</if>
) values(#{username},#{password}
<if test="photo != null">
,#{photo}
</if>
)
</insert>
5.2 trim 标签
用来去掉不必要的信息;如果所有字段都是非必填项,就考虑使用 <trim> 标签结合 <if> 标签,对多个字段都采取动态生成的方式
<trim> 标签的属性:
- prefix:表示整个语句块,以 prefix 的值作为前缀
- suffix:表示整个语句块,以suffix的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
<!--trim 标签-->
<insert id="add3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
username,
</if>
<if test="password!=null">
password,
</if>
<if test="photo!=null">
photo,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="photo!=null">
#{photo},
</if>
</trim>
</insert>
- prefix 这个操作相当于在 trim 前面加 (
- suffix 这个操作相当于在 trim 后面加 )
- 多个 <if>组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixOverrides 配置去掉最后⼀个
测试类:
@Test
void add3() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("王五");
userinfo.setPassword("123");
int result = userMapper.add2(userinfo);
System.out.println("执行结果:" + result);
}
5.3 where 标签
- 传⼊的用户对象,根据属性做 where 条件查询,用户对象中属性不为 null 的,都为查询条件。
- <where> 标签会判断里面是否有内容,如果有会动态生成 where 的 sql 语句;如果没有,则 where 关键字不会生成
- 去除 最前面的 and 关键字
UserMapper 接⼝中新增条件查询方法:
//where 标签
List<Userinfo> getListByWhere(Userinfo userinfo);
UserMapper.xml 中新增条件查询 sql:
<select id="getListByWhere" resultType="com.example.demo.model.Userinfo">
select * from userinfo
<where>
<if test="id>0">
id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
<if test="password!=null">
and password=#{password}
</if>
</where>
</select>
测试类1(什么都不传,相当于没有 where 语句):
@Test
void getListByWhere() {
Userinfo userinfo = new Userinfo();
List<Userinfo> list = userMapper.getListByWhere(userinfo);
System.out.println(list);
}
测试类2(传入id,正常添加 where语句):
@Test
void getListByWhere() {
Userinfo userinfo = new Userinfo();
userinfo.setId(1);
List<Userinfo> list = userMapper.getListByWhere(userinfo);
System.out.println(list);
}
测试类3(where 语句可以去掉前面的 and):
@Test
void getListByWhere() {
Userinfo userinfo = new Userinfo();
//userinfo.setId(1);
userinfo.setUsername("王五");
//userinfo.setPassword("123");
List<Userinfo> list = userMapper.getListByWhere(userinfo);
System.out.println(list);
}
上述 where标签也可以使用 trim 标签:
<trim prefix="where" prefixOverrides="and">
</trim>
trim 里边执行到了加 where,执行不到不加 where
5.4 set 标签
- 根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤<set>标签来指定动态内容
- <set> 标签会判断里面是否有内容,如果有会动态生成 set 的 sql 语句;如果没有,则 set 关键字不会生成
- 去掉最后面的逗号(,)
UserMapper 接⼝中新增条件查询方法:
//set 标签
int update2(Userinfo userinfo);
UserMapper.xml 中新增条件查询 sql:
<!--set 标签-->
<update id="update2">
update userinfo
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
password=#{password},
</if>
</set>
where id=#{id}
</update>
测试类:
@Test
void update2() {
Userinfo userinfo = new Userinfo();
userinfo.setId(3);
userinfo.setUsername("王五");
int result = userMapper.update2(userinfo);
System.out.println("执行结果:" + result);
}
5.5 foreach 标签
<foreach>标签有如下属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
根据多个⽂章 id 来删除⽂章数据。
ArticleMapper 中新增接口方法:
int deleteByIds(List<Integer> ids);
ArticleMapper.xml 中新增删除 sql:
<delete id="deleteByIds">
delete from article
where id in
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>