网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
// wrapper.notInSql(“name”,“select name from user where age>21”);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
##### 4)分组与排序
* 1)分组:
通过Wrapper.query()构建的查询字段默认是表中的所有字段,因此在这种情况下分组是没有意义的,分组具体的用法我们后面再详细介绍;
/**
* 分组
*/
@Test
public void test4() {
// 创建wrapper对象
QueryWrapper<User> wrapper = Wrappers.query();
/\*
相当于 select * from user where gropu sex
这是一个没有意义的分组,分组必须结合查询的字段来体现,后续学QueryWrapper的select方法再介绍
*/
wrapper.groupBy(“sex”);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 2)having操作
/**
* having操作
*/
@Test
public void test5() {
// 创建wrapper对象
QueryWrapper<User> wrapper = Wrappers.query();
// group by sex having sex = 0
wrapper.groupBy("sex");
wrapper.having("sex", "0");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 3)排序:
/**
* 排序
*/
@Test
public void test6() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
/\*\*
* 参数1: 是否是Asc排序(升序), true : asc排序, false: desc排序
* 参数2: 排序的字段
*/
// wrapper.orderByAsc(“age”); // order by age asc
// wrapper.orderByDesc(“age”); // order by age desc
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
##### 5)多条件的拼接
Wrapper对象在调用每一个方法时都会返回当前对象(Wrapper),这样可以很好的方便我们链式编程;另外MyBatis Plus在拼接多个条件时默认使用and拼接,如果需要使用or,那么需要显示的调用or()方法;
* 1)and拼接条件:
/**
* and拼接条件
*/
@Test
public void test7() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
/\*
默认情况下,多条件是以and拼接
SQL语句: name LIKE “%a%” AND age > 20 AND sex = 0
*/
wrapper.like(“name”, “%a%”)
.lt(“age”, 20)
.eq(“sex”, 0);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 2)or拼接条件:
/**
* or拼接条件
*/
@Test
public void test8() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
/\*
默认情况下,多条件是以and拼接
SQL语句: name LIKE “%a%” OR age > 20 AND sex = 0
*/
wrapper.like(“name”, “%a%”)
.or()
.lt(“age”, 20)
.eq(“sex”, 0); // 该条件仍然以AND拼接
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
##### 6)Wrapper的其他方法
* 1)and方法:用于拼接一个其他的整体条件;示例如下:
/**
* and方法
*/
@Test
public void test1() {
QueryWrapper wrapper = Wrappers.query();
// 生成的SQL为: (age < ? AND (sex = ? OR name LIKE ?))
/\*
wrapper.lt(“age”, 20);
wrapper.and(new Consumer<QueryWrapper>() {
@Override
public void accept(QueryWrapper userQueryWrapper) {
userQueryWrapper.eq(“sex”, 0)
.or()
.like(“name”, “J”);
}
});
*/
// 生成的SQL为: (age < ? AND sex = ? OR name LIKE ?)
wrapper.lt("age", 20)
.eq("sex", 0)
.or()
.like("name", "J");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 2)func方法:用于多条件的拼接,直接使用之前的方法也可以做到这个功能;示例如下:
/**
* func方法
*/
@Test
public void test2() {
QueryWrapper wrapper = Wrappers.query();
// 生成的SQL语句条件: (age < ? AND name LIKE ? AND sex = ?)
wrapper.lt("age", 20);
wrapper.func(new Consumer<QueryWrapper<User>>() {
@Override
public void accept(QueryWrapper<User> userQueryWrapper) {
userQueryWrapper.like("name", "a");
userQueryWrapper.eq("sex", "0");
}
});
// 等价于:
// wrapper.lt(“age”, 20).like(“name”, “a”).eq(“sex”, “0”);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 3)nested方法:功能等价于and方法
/**
* nested方法
*/
@Test
public void test3() {
QueryWrapper wrapper = Wrappers.query();
// 生成的SQL语句条件为: (id = ? AND (name LIKE ? OR age > ?))
// nested()等价于and方法()
wrapper.eq("id", 1);
wrapper.nested(new Consumer<QueryWrapper<User>>() {
@Override
public void accept(QueryWrapper<User> userQueryWrapper) {
// 默认情况下是用and来拼接多个条件
userQueryWrapper
.like("name", "a")
.or()
.gt("age", 20);
}
});
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 4)apply方法:可以使用占位符进行参数传参;示例如下:
/**
* apply方法
*/
@Test
public void test4() {
QueryWrapper wrapper = Wrappers.query();
// SQL: (name like ? and age > ?)
// wrapper.apply(“name like {0} and age > {1}”, “%J%”, 18);
// SQL: (date\_format(birthday, '%Y-%m-%d') = ?)
wrapper.apply("date\_format(birthday, '%Y-%m-%d') = {0}", "2001-10-04");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 4)last方法:无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险
>
> Tips:apply方法可以防止SQL注入,但last方法不能;
>
>
>
@Test
public void test13() {
QueryWrapper wrapper = Wrappers.query();
// 无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险
// SQL: name = 'Jone'
// String name = “Jone”;
// wrapper.last(“where name = '” + name + “'”);
// SQL: SELECT id,name,sex,age FROM user where name ='' or 1=1; -- '
String name = "' or 1=1; -- ";
wrapper.last("where name ='" + name + "'"); // 出现SQL注入问题
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 5)exists方法:用于exists语句
/**
* exists方法
*/
@Test
public void test6() {
QueryWrapper wrapper = Wrappers.query();
// SQL: (EXISTS (select 1))
wrapper.exists("select 1");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
* 6)notExists方法:
/**
* notExists方法
*/
@Test
public void test7() {
QueryWrapper wrapper = Wrappers.query();
// SQL: (NOT EXISTS (select 1))
wrapper.notExists("select 1");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
#### 3.3.2 QueryMapper
QueryMapper是AbstractWrapper的子类,主要用于查询指定字段,方法列表如下:
| 方法名 | 解释 | 示例 |
| --- | --- | --- |
| select(String… sqlSelect) | 设置查询字段 | 例1:select(“id”, “name”, “age”) 例2:select(i -> i.getProperty().startsWith(“test”)) |
* 示例代码:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo04_QueryWrapper {
@Autowired
private UserMapper userMapper;
// 选择查询的字段
@Test
public void test1() throws Exception {
QueryWrapper<User> wrapper = Wrappers.query();
// 确定要查询的字段
wrapper.select("id", "name", "sex");
// in
wrapper.in("id","1","2");
// SQL: SELECT id,name,sex FROM user WHERE (id IN (?,?))
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
@Test
public void test2() throws Exception {
User user = new User();
user.setId(1L);
// user当做查询的条件
QueryWrapper<User> wrapper = Wrappers.query(user);
// 指定查询的列
wrapper.select("id", "name", "sex");
// SQL: SELECT id,name,sex FROM user WHERE id=?
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
}
#### 3.3.3 UpdateWrapper
UpdateWrapper也是AbstractWrapper的子类,因此UpdateWrapper也具备之前的那些查询方法,不同的是,UpdateMapper在那些方法基础之上还提供了很多有关于更新操作的方法;
* 方法如下:
| 方法名 | 解释 | 示例 |
| — | — | — |
| set(String column, Object val) | 设置查询字段 | 例1:`set("name", "老李头")`
例2:`set("name", "")`
—>数据库字段值变为**空字符串**例3:`set("name", null)`
—>数据库字段值变为`null` |
| setSql(String sql) | 设置set子句的部分SQL | 例1:`setSql("name = '老李头'")`
例2:`setSql("name = '老李头',age=20 where id=1")` |
示例代码:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo05_UpdateWrapper {
@Autowired
private UserMapper userMapper;
@Test
public void test1() throws Exception {
UpdateWrapper<User> wrapper = Wrappers.update();
// UpdateWrapper也是AbstractWrapper的子类,因此也具备一些基本的查询方法
wrapper.like("name", "J");
List<User> userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
/\*\*
* 第一种方法: 使用wrapper来修改,并且指定查询条件
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.set(“name”, “Jackson”);
wrapper.set(“age”, “16”);
wrapper.set(“sex”, “1”);
wrapper.eq(“id”, 2L);
// SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
userMapper.update(null, wrapper);
}
/\*\*
* 第二种方法: 使用wrapper来封装条件,使用entity来封装修改的数据
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.eq(“id”, 2L);
User user = new User(null, "Jack", "0", 28);
// SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
userMapper.update(user, wrapper);
}
/\*\*
* 第三种方法: Wrappers.update(user)传递查询的条件,使用wrapper来修改
*
* @throws Exception
*/
@Test
public void test4() throws Exception {
User user = new User();
user.setId(1L);
// user当做查询条件
UpdateWrapper<User> wrapper = Wrappers.update(user);
wrapper.set("name", "xiaohui");
wrapper.set("sex", "0");
wrapper.set("age", "22");
// SQL : UPDATE user SET name=?,sex=?,age=? WHERE id=?
userMapper.update(null, wrapper);
}
/\*\*
* setSql方法
*
* @throws Exception
*/
@Test
public void test5() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.setSql(“name=‘abc’,sex=‘0’,age=18 where id=1”);
// SQL: UPDATE user SET name='abc',sex='0',age=18 where id=1
userMapper.update(null, wrapper);
}
}
#### 3.3.4 LambdaQueryWrapper
LambdaQueryWrapper是QueryWrapper的子类,具备QueryWrapper的所有方法,QueryWrapper的方法上提供了一系列有关于方法引的操作;
* 使用示例:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_LambdaQueryMapper {
@Autowired
private UserMapper userMapper;
/\*\*
* 使用QueryWrapper
* @throws Exception
*/
@Test
public void test1() throws Exception {
QueryWrapper<User> wrapper = Wrappers.query();
wrapper.eq("id","1");
List<User> userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
/\*\*
* 使用LambdaQueryWrapper
* @throws Exception
*/
@Test
public void test2() throws Exception {
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
// id=1
// wrapper.eq(User::getId,1);
// select id,name,age from user where id in (1,2,3) and name like "%a%"
wrapper.in(User::getId,"1","2","3")
.like(User::getName,"a")
.select(User::getId,User::getName,User::getAge);
List<User> userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
}
#### 3.3.5 LambdaUpdateMapper
LambdaUpdateMapper同样是UpdateMapper的子类,具备UpdateMapper的所有方法,UpdateMapper的方法上提供了一系列有关于方法引的操作;
* 示例代码:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_LambdaUpdateWrapper {
@Autowired
private UserMapper userMapper;
/\*\*
* 使用UpdateWrapper
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
UpdateWrapper<User> wrapper = Wrappers.update();
wrapper.eq("id", "1");
wrapper.set("name", "xiaohui");
wrapper.set("age", 20);
userMapper.update(null, wrapper);
}
/\*\*
* 使用LambdaUpdateWrapper
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(User::getId,"1");
wrapper.set(User::getName,"xiaolan");
wrapper.set(User::getAge,18);
userMapper.update(null,wrapper);
}
}
### 3.3 Mapper的分页查询
#### 3.3.1 Mapper分页查询配置
在MyBatis中提供有Page对象来帮助我们实现分页查询,在Page对象中有如下成员:
| 成员变量 | 说明 |
| --- | --- |
| List getRecords() | 当前页数据 |
| public long getTotal() | 总记录数 |
| public long getSize() | 页大小 |
| public long getCurrent() | 当前页 |
| default long getPages() | 总页数 |
| public boolean hasNext() | 是否有上一页 |
| public boolean hasPrevious() | 是否有上一页 |
MyBatisPlus的分页逻辑底层是通过分页插件来完成的,因此我们首先要配置MyBatisPlus的分页插件;
* 配置分页插件:
package com.dfbz.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Configuration
@MapperScan(“com.dfbz.mapper”) // mapper接口的所在位置
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
#### 3.3.2 Mapper完成分页查询
在BaseMapper中主要提供有如下方法来完成分页查询:
* `<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)`:
+ 参数1:分页配置类
+ 参数2:分页查询条件
+ 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为指定类型
* `<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)`
+ 参数1:分页配置类
+ 参数2:分页查询条件
+ 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为Map类型
---
* 1)无条件分页查询:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_BaseMapper的分页查询 {
@Autowired
private UserMapper userMapper;
/\*\*
* 无条件分页查询
* @throws Exception
*/
@Test
public void test1() throws Exception {
// 封装分页信息
Page<User> page = new Page<>(1,3);
/\*
执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, null);
// 当前页数据
List<User> pageData = page.getRecords();
for (User user : pageData) {
System.out.println(user);
}
System.out.println("------------");
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
}
}
* 查询结果:
首先查询总记录数
> Preparing: SELECT COUNT() FROM user
> Parameters:
< Columns: COUNT()
< Row: 10
<== Total: 1
再查询分页
> Preparing: SELECT id,name,sex,age FROM user LIMIT ?
> Parameters: 3(Long)
< Columns: id, name, sex, age
< Row: 1, Jone, 1, 27
<== Row: 2, Jack, 0, 20
<== Row: 3, Tom, 1, 28
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@226eba67]
User(id=1, name=Jone, sex=1, age=27)
User(id=2, name=Jack, sex=0, age=20)
User(id=3, name=Tom, sex=1, age=28)
当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:28:16.946 INFO 8724 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated…
2022-11-15 15:28:16.948 INFO 8724 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
Process finished with exit code 0
* 带条件分页查询:
/**
* 带条件分页查询
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
QueryWrapper<User> wrapper = Wrappers.query();
wrapper.like("name", "a");
// 封装分页信息
Page<User> page = new Page<>(1, 3);
/\*
执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, wrapper);
// 当前页数据
List<User> pageData = page.getRecords();
for (User user : pageData) {
System.out.println(user);
}
System.out.println("------------");
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
}
* 查询结果:
> Preparing: SELECT COUNT() FROM user WHERE (name LIKE ?)
> Parameters: %a%(String)
< Columns: COUNT()
< Row: 5
<== Total: 1
> Preparing: SELECT id,name,sex,age FROM user WHERE (name LIKE ?) LIMIT ?
> Parameters: %a%(String), 3(Long)
< Columns: id, name, sex, age
< Row: 2, Jack, 0, 20
<== Row: 4, Sandy, 1, 21
<== Row: 6, Jackson, 0, 18
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58a2b4c]
User(id=2, name=Jack, sex=0, age=20)
User(id=4, name=Sandy, sex=1, age=21)
User(id=6, name=Jackson, sex=0, age=18)
当前页:1
每页显示的条数:3
总记录数:5
总页数:2
是否有上一页:false
是否有下一页:true
* 3)将分页数据的查询结果以Map类型返回
/**
* 查询结果以Map返回
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
// 封装分页信息
Page page = new Page<>(1, 3);
userMapper.selectMapsPage(page, null);
// 每一条记录都是一个HashMap
List<HashMap<String,Object>> pageData = page.getRecords();
for (HashMap userMap : pageData) {
System.out.println(userMap);
}
System.out.println("------------");
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
}
* 查询结果:
> Preparing: SELECT COUNT() FROM user
> Parameters:
< Columns: COUNT()
< Row: 10
<== Total: 1
> Preparing: SELECT id,name,sex,age FROM user LIMIT ?
> Parameters: 3(Long)
< Columns: id, name, sex, age
< Row: 1, Jone, 1, 27
<== Row: 2, Jack, 0, 20
<== Row: 3, Tom, 1, 28
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7123be6c]
{sex=1, name=Jone, id=1, age=27}
{sex=0, name=Jack, id=2, age=20}
{sex=1, name=Tom, id=3, age=28}
当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:50:37.905 INFO 20980 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated…
2022-11-15 15:50:37.905 INFO 20980 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
Process finished with exit code 0
### 3.4 MyBatis Plus的Service查询
#### 3.4.1 通用Service简介
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行、remove删除、list 查询集合、page查询分页
>
> 官网案例:<https://baomidou.com/pages/49cc81/>
>
>
>
使用步骤:
* 1)定义一个UserService接口继承与MyBatisPlus提供的IService接口:
package com.dfbz.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dfbz.entity.User;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public interface IUserService extends IService {
}
* 2)定义一个UserService的实现类,并且继承与MyBatisPlus提供的ServiceImpl:
package com.dfbz.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import com.dfbz.service.IUserService;
import org.springframework.stereotype.Service;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Service(“userService”)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
#### 3.4.2 通用service常用方法
* 新增:
+ `default boolean save(T entity)`:新增记录
+ `boolean saveBatch(Collection<T> entityList)`:批量插入
+ `saveBatch(Collection<T> entityList, int batchSize)`:一次性批量插入batchSize条记录
* 删除:
+ `boolean removeById(Serializable id)`:根据id删除
+ `boolean removeByMap(Map<String, Object> columnMap)`:根据条件删除
+ `boolean remove(Wrapper<T> queryWrapper)`:使用Wrapper封装条件删除
+ `boolean removeByIds(Collection<? extends Serializable> idList)`:删除一批
* 修改:
+ `boolean updateById(T entity)`:修改
+ `boolean update(Wrapper<T> updateWrapper)`:根据Wrapper修改
+ `boolean update(T entity, Wrapper<T> updateWrapper)`:使用Wrapper查询出结果,修改为entity
+ `boolean updateBatchById(Collection<T> entityList)`:批量修改
+ `updateBatchById(Collection<T> entityList, int batchSize)`:一次性批量修改batchSize条记录
+ `boolean saveOrUpdate(T entity)`:如果id存在则修改,如果id不存在则新增
* 查询:
+ `T getById(Serializable id)`:根据id查询
+ `List<T> listByIds(Collection<? extends Serializable> idList)`:根据一批id查询多条记录
+ `List<T> listByMap(Map<String, Object> columnMap)`:根据条件查询多条记录
+ `T getOne(Wrapper<T> queryWrapper)`:根据Wrapper查询一条记录,如果查询到多条则抛出异常
+ `T getOne(Wrapper<T> queryWrapper, boolean throwEx)`:根据Wrapper查询一条记录,通过throwEx决定是否抛出异常
+ `int count()`:查询总记录数
+ `int count(Wrapper<T> queryWrapper)`:根据条件查询总记录数
* 分页:
+ `<E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper)`:带条件分页查询,当前页数据为T类型
+ `<E extends IPage<T>> E page(E page)`:无条件分页
+ `List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper)`:带条件分页查询,当前页数据为HashMap类型
+ `List<Map<String, Object>> listMaps()`:无条件分页
---
* 测试代码:
package com.dfbz;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_Service查询 {
@Autowired
private IUserService userService;
/\*\*
* 新增
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
User user = new User(null, “xiaohui”, “0”, 20);
userService.save(user);
}
/\*\*
* 如果id存在则修改,不存在则新增
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
User user = new User(1L, “xiaohui”, “0”, 20);
userService.saveOrUpdate(user);
}
/\*\*
* 根据id删除
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
userService.removeById(1L);
}
/\*\*
* 根据id修改
*
* @throws Exception
*/
@Test
public void test4() throws Exception {
User user = new User(1L, "xiaolan", "1", 18);
userService.updateById(user);
}
/\*\*
* 根据id查询
*
* @throws Exception
*/
@Test
public void test5() throws Exception {
User user = userService.getById(1L);
System.out.println(user);
}
/\*\*
* 查询列表
*
* @throws Exception
*/
@Test
public void test6() throws Exception {
QueryWrapper<User> wrapper = Wrappers.query();
wrapper.in("id", "1", "2");
// 查询所有
// List userList = userService.list();
// 通过wrapper查询
List<User> userList = userService.list(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
/\*\*
* 查询总记录数
*
* @throws Exception
*/
@Test
public void test7() throws Exception {
QueryWrapper<User> wrapper = Wrappers.query();
wrapper.like("name", "a");
// 查询总记录数
// int count = userService.count();
// 根据条件查询总记录数
int count = userService.count(wrapper);
System.out.println(count);
}
/\*\*
* 分页查询(当前页类型为指定类型)
*
* @throws Exception
*/
@Test
public void test8() throws Exception {
Page<User> page = new Page<>(1, 3);
userService.page(page);
// 当前页数据
List<User> pageData = page.getRecords();
for (User user : pageData) {
System.out.println(user);
}
System.out.println("------------");
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
}
/\*\*
* 分页查询(当前页结果为HashMap类型)
*
* @throws Exception
*/
@Test
public void test9() throws Exception {
Page page = new Page<>(1, 3);
userService.pageMaps(page);
// 当前页数据
List<HashMap<String, Object>> pageData = page.getRecords();
for (HashMap userMap : pageData) {
System.out.println(userMap);
}
System.out.println("------------");
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
}
/\*\*
* 链式查询
*
* @throws Exception
*/
@Test
public void test10() throws Exception {
QueryChainWrapper<User> chainWrapper = userService.query();
// SQL: SELECT id,name,age FROM user WHERE (id IN (?,?,?) AND name LIKE ?)
List<User> userList = chainWrapper.select("id", "name", "age")
.in("id", "1", "2", "3")
.like("name", "a")
.list();
for (User user : userList) {
System.out.println(user);
}
}
/\*\*
* 链式修改
*
* @throws Exception
*/
@Test
public void test11() throws Exception {
UpdateChainWrapper<User> chainWrapper = userService.update();
// SQL: UPDATE user SET age=? WHERE (id IN (?,?) OR sex = ?)
chainWrapper.in("id","1","2")
.or()
.eq("sex","0")
.set("age",20).
update();
}
}
### 3.5 MyBatisPlus的常用注解
#### 3.5.1 @TableName
操作数据库表时,Mapper接口继承BaseMapper,泛型名和数据库表名对应,如果数据表名为t\_user,而BaseMapper的泛型为实体类User,导致找不到数据库的表。
* 1)解决方法1:实体类使用@TableName注解,value值为表名
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(“t_user”)
public class User {
private Long id;
private String name;
private String sex;
private Integer age;
}
* 2)解决方法2:如果多张表的表名为t\_user/t\_cat/t\_xxx,不需要为每一个实体类添加@TableName注解,在MyBatis全局配置即可,为所有表名添加前缀
mybatis-plus:
global-config: # MyBatisPlus全局配置
db-config: # 配置数据库
table-prefix: t_ #配置表名前缀为t_
#### 3.5.2 @TableId
MyBatisPlus在实现CRUD默认会将Id作为主键,在插入数据时,如果主键不叫Id则添加功能会失败
解决方案:@TableId注解标识属性,将此属性对应的字段指定为主键
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(“t_user”)
public class User {
// 将当前属性所对应的字段作为主键
@TableId
private Long id;
private String name;
private String sex;
private Integer age;
}
##### 1)value属性
问题:实体类中被标识为主键的属性名为id,而数据库的主键为uid,则id属性不会对应uid字段上
解决方案:使用@TableId的value属性设置当前主键字段的字段名为uid
package com.dfbz.entity;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(value = "uid")
private Long id;
private String name;
private String sex;
private Integer age;
}
##### 2)主键策略
MybatisPlus为我们支持了许多种的主键策略;
主键策略是指Mybatis-plus可以自动生成主键的策略,不需要手动插入主键,由MybatisPlus的主键策略帮我们自动生成主键
>
> 官网资料:[https://baomidou.com/pages/e131bd/#spring-boot)](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)
>
>
>
在枚举类IdType中定制有如下几种注解策略:
public enum IdType {
// 数据自增(该类型请确保数据库设置了 ID自增 否则无效)
AUTO(0),
// 采用雪花算法生成ID
NONE(1),
// 交由用户自己分配ID(必须分配,不分配则出现异常,除非数据库表本身设置了自增)
INPUT(2),
// 采用雪花算法((主键类型为number或string)
// 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
ASSIGN\_ID(3),
// 分配UUID (主键类型为 string)
// 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
ASSIGN\_UUID(4),
// 不推荐使用,推荐使用ASSIGN\_ID
@Deprecated
ID\_WORKER(3),
// 不推荐使用,推荐使用ASSIGN\_ID
@Deprecated
ID\_WORKER\_STR(3),
// 不推荐使用,推荐使用ASSIGN\_UUID
@Deprecated
UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
* 实体类:
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
// 使用数据库自增策略
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String sex;
private Integer age;
}
* 全局配置自增策略:
mybatis-plus:
global-config: # MyBatisPlus全局配置
db-config: # 配置数据库
id-type: auto # 统一设置id生成策略
测试代码:
package com.dfbz;
import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo08_常用注解 {
@Autowired
private IUserService userService;
@Test
public void test1() throws Exception {
User user = new User(111L, "xiaohui", "0", 20);
userService.save(user);
}
}
#### 3.5.3 @TableField
如果实体类的普通属性名,和数据库非主键的字段名不一致解决方案:@TableField设置对应字段名
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user\_name")
private String name;
@TableField("user\_sex")
private String sex;
@TableField("user\_age")
private Integer age;
}
#### 3.5.4 @TableLogic
在实际开发中,我们删除一条记录可能只是修改其状态,并不是真正的从数据库中删除掉;这样的删除成为逻辑删除;
* 逻辑删除:表中设置字段为删除状态 比如删除为1 未删除为0 则查询时,只会查到状态为0的数据(可以进行数据恢复)。
* 物理删除:从表中删除。
准备一张数据表:
DROP TABLE IF EXISTS emp
;
CREATE TABLE emp
(
id
bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键ID’,
name
varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘姓名’,
is\_delete
int(11) NULL DEFAULT NULL COMMENT ‘是否删除 0:未删除 1:删除’,
PRIMARY KEY (id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
– Records of emp
INSERT INTO emp
VALUES (1, ‘xiaohui’, 0);
INSERT INTO emp
VALUES (2, ‘xiaolan’, 0);
实体类:
package com.dfbz.entity;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
/\*
表示该列为逻辑删除字段
0: 表示未删除
1: 表示已删除
*/
@TableLogic
private Integer isDelete;
}
测试代码:
@Autowired
private EmpMapper empMapper;
@Test
public void test2() throws Exception {
// 只是逻辑删除(把id为1的记录的is\_delete改为1)
empMapper.deleteById(1L);
}
执行代码,观察日志:

>
> Tips:本质上就是执行了一个update
>
>
>
当实体类中有标注逻辑删除字段时,在查询时是不会查询被逻辑删除的记录的,示例代码:
@Test
public void test3() throws Exception {
// 不会查询is\_delete为1的记录
List<Emp> empList = empMapper.selectList(null);
for (Emp emp : empList) {
System.out.println(emp);
}
}
执行结果:

#### 3.5.5 @EnumValue
在数据库中,经常会有一些字段符合枚举类型;
例如:
* sex:0代表男,1代表女
* status:0代表未开始,1代表进行中,2代表已结束
* is\_secret:0代表私密,1代表公开等
* is\_mark:0代表未签收,1代表拒签,2代表已签收
我们通常都是将以上字段设置为char或者int类型,然后通过对应的字符/数字代表对应的含义,然后到Java代码中我们通常都需要做类似于如下的判断:
${sex==0?‘男’:‘女’}
<if test=
s
t
a
t
u
s
=
=
0
>
未开始
<
/
i
f
>
<
i
f
t
e
s
t
=
{status==0}>未开始</if> <if test=
status==0>未开始</if><iftest={status1}>进行中
<if test=${status2}>已结束
if(status=0){
// 未开始…
}else if(status1){
// 进行中
} else if(status2){
已结束
}
以上代码比较繁琐,并且可读性不是很高;
在MyBatisPlus中支持我们定义一个枚举与数据库中的字段对应起来,然后在枚举类中,使用@EnumValue注解标注真实的值(与数据库的字段对应),然后定义一个String类型的字段表示这个枚举项代表的字符串含义;
* 准备一张数据表:
DROP TABLE IF EXISTS student
;
CREATE TABLE student
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘姓名’,
sex
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘性别 0:男 1:女’,
PRIMARY KEY (id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
– Records of student
INSERT INTO student
VALUES (1, ‘小灰’, ‘0’);
INSERT INTO student
VALUES (2, ‘小蓝’, ‘1’);
* 定义性别枚举:
package com.dfbz.enmu;
import com.baomidou.mybatisplus.annotation.EnumValue;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum SexEnum {
MAN(0, “男”),
WOMAN(1, “女”);
// 这一列的值和数据库表映射
@EnumValue
private Integer sexValue;
// 这个字段就是这个枚举项的字符串含义
private String sexName;
private SexEnum(Integer sexValue, String sexName) {
this.sexValue = sexValue;
this.sexName = sexName;
}
}
* 扫描枚举包:
#配置日志
mybatis-plus:
扫描枚举包
type-enums-package: com.dfbz.enum_
* 定义实体类:
package com.dfbz.entity;
import com.dfbz.enmu.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
/\*
使用枚举类型
当从数据库中查询到了0则赋值SexEnum.MAN给sex变量
当从数据库中查询到了1则赋值SexEnum.WOMAN给sex变量
*/
private SexEnum sex;
}
* 定义Mapper接口:
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Repository
public interface StudentMapper extends BaseMapper {
}
* 测试代码:
@Autowired
private StudentMapper studentMapper;
@Test
public void test4() throws Exception {
System.out.println(studentMapper.selectById(1L));
System.out.println(studentMapper.selectById(2L));
}

虽然sex字段为变为了枚举类型,但是将搜索条件设置为sex=0依旧可以搜索到符合条件的记录:
@Test
public void test5() throws Exception {
QueryWrapper<Student> wrapper = Wrappers.query();
// 搜索条件为0依旧可以搜索出来
wrapper.eq("sex","0");
List<Student> stuList = studentMapper.selectList(wrapper);
for (Student student : stuList) {
System.out.println(student);
}
}

#### 3.5.6 @Version
##### 1)乐观锁与悲观锁
在多个客户端(线程/进程)操作同一个资源并发生写的操作时,我们就需要保证数据的安全性了。
* 如图所示:
当我们在购买车票时,首先会进行票数的查询,例如A1在购买车票时查询到票还有100张,准备购买第100张票,与此同时,A2也查询到票数为100,也将购买第100张票;

>
> Tips:**上述图中,两个窗口在买票之前分别查询了票,发现票数余额为100,然后都卖了第100张票,出现多卖情况**
>
>
>
在并发修改某一资源时,我们必须保证线程安全的问题。在操作之前先加锁,这种方式就是采用悲观锁的方式;
* **悲观锁的概念**:悲观锁简单的理解就是程序处于悲观状态,在操作任何资源时都认为其他程序会来修改当前资源,自己不放心,因此在操作资源时加锁。

悲观锁虽然保证了程序的安全性,同时效率也**降低**了很多,在一个客户端操作时,其他客户端均不可操作,降低了系统的并发性能。
* **乐观锁概念**:乐观锁简单理解就是程序一直处于乐观状态,在操作任何资源时认为其他程序不会来修改当前资源,整个过程不加锁,**不加锁效率是可以保证,但不是又回到了我们最初的那个状态吗?即线程安全问题**。
我们可以在每条记录中分配一个`_version`字段,每当我们对记录进行更新时,此版本号都会自增。我们可以借助该字段帮我们完成乐观锁。保证线程安全问题。

实现方式:
– 要修改数据之前,先查该数据上一次修改的时间戳
select version from table_ where id=1;
– 修改数据时,更新版本号
update table_ set goods_name=‘小苹果’, version=version+1 where version=${version};
##### 2)@Version实现乐观锁
### 给大家的福利
**零基础入门**
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:

因篇幅有限,仅展示部分资料
网络安全面试题

绿盟护网行动

还有大家最喜欢的黑客技术

**网络安全源码合集+工具包**


**所有资料共282G**,朋友们如果有需要全套《网络安全入门+黑客进阶学习资源包》,可以扫描下方二维码领取(如遇扫码问题,可以在评论区留言领取哦)~
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**