目录
1.MyBatis 框架的搭建
1.1 创建数据库和表
1.2 添加 MyBatis 依赖
1.3 设置 MyBatis 配置
1.3.1 设置数据库的连接信息
1.3.2 设置 XML 保存路径和命名格式
1.4 根据 MyBatis 写法完成数据库得操作
1.4.1 定义接口
1.4.2 使用 XML 实现接口
2.MyBatis查询操作
2.1 单表查询
2.1.1 参数占位符 #{ } 和 ${ }(常见面试问题)
从上边的事例可以看出 ${ } 可以实现的功能 #{ } 都能实现,并且 ${ } 还有 SQL 注入的问题,那么为什么 ${ } 的写法还存在?
2.1.2 ${ } 的优点
2.2 类中的属性和数据库表中的字段名不一致时,那么查询结果为 null,解决方案
2.2.1 将类中的属性和表中的字段名保持一致(最简单的解决方案)
2.2.2 使用 sql 语句中的 as 进行列名(字段名)重命名,让列名(字段名)等于属性名
2.2.3 返回字典映射:resultMap
2.3 like 查询操作
2.3.1 解决方案一:去掉单引号
2.3.2 解决方案二:mysql 内置函数 concat
3. 增、删、改操作
3.1 删除操作 + @Transactional 注解
3.2 修改用户操作
3.3 添加用户操作
3.3.1 MyBatis 添加操作,返回受影响的行数
3.3.2 返回自增 ID
4. 多表查询
4.1 一对一映射:使用注解
4.2 一对多映射:文章案例
5.动态 SQL
5.1 if 标签
5.2 trim 标签
5.3 where 标签
5.4 set 标签
5.5 foreach 标签
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>