3.3.1 Wrapper的基本方法
AbstractWrapper是其他常用Wrapper的父类,用于生成 sql 的 where 条件
1)方法介绍
AbstractWrapper提供了很多公有的方法,其子类全部具备这些方法,方法列表如下:
方法名 | 解释 | 示例 |
---|---|---|
eq | 等于 = | eq(“name”, “老王”)**--->** name = ‘老王’ |
ne | 不等于 <> | ne(“name”, “老王”)**--->** name <> ‘老王’ |
gt | 大于 > | gt(“age”, 18)**--->** age > 18 |
ge | 大于等于 >= | ge(“age”, 18)**--->** age >= 18 |
lt | 小于 < | lt(“age”, 18)**--->** age < 18 |
le | 小于等于 <= | le(“age”, 18)**--->** age <= 18 |
between | between 值1 and 值2 | between(“age”, 18, 30)**--->** age between 18 and 30 |
notBetween | not between 值1 and 值2 | notBetween(“age”, 18, 30)**--->** age not between 18 and 30 |
like | LIKE ‘%值%’ | like(“name”, “王”)**--->** name like ‘%王%’ |
notLike | NOT LIKE ‘%值%’ | notLike(“name”, “王”)**--->** name not like ‘%王%’ |
likeLeft | LIKE ‘%值’ | likeLeft(“name”, “王”)**--->** name like ‘%王’ |
likeRight | LIKE ‘值%’ | likeRight(“name”, “王”)**--->** name like ‘王%’ |
isNull | 字段 IS NULL | isNull(“name”)**--->** name is null |
isNotNull | 字段 IS NOT NULL | isNotNull(“name”)**--->** name is not null |
in | 字段 IN (v0, v1, …) | in(“age”, 1, 2, 3)**--->** age in (1,2,3) |
notIn | 字段 NOT IN (v0, v1, …) | notIn(“age”, 1, 2, 3)**--->** age not in (1,2,3) |
inSql | 字段 IN ( sql语句 ) | inSql(“id”, “select id from table where id < 3”)**--->** id in (select id from table where id < 3) |
notInSql | 字段 NOT IN ( sql语句 ) | notInSql(“id”, “select id from table where id < 3”)**--->** id not in (select id from table where id < 3) |
groupBy | 分组:GROUP BY 字段, … | groupBy(“id”, “name”)**--->** group by id,name |
orderByAsc | 排序:ORDER BY 字段, … ASC | orderByAsc(“id”, “name”)**--->** order by id ASC,name ASC |
orderByDesc | 排序:ORDER BY 字段, … DESC | orderByDesc(“id”, “name”)**--->** order by id DESC,name DESC |
orderBy | 排序:ORDER BY 字段, … | orderBy(true, true, “id”, “name”)**--->** order by id ASC,name ASC |
having | HAVING ( sql语句 ) | 例1:having(“sum(age) > 10”)**--->** having sum(age) > 10例2:having(“sum(age) > {0}”, 11)**--->** having sum(age) > 11 |
func | 主要解决条件拼接 | func(i -> if(true) {i.eq(“id”, 1)} else {i.ne(“id”, 1)}) |
or | 拼接 OR | eq(“id”,1).or().eq(“name”,“老王”)**--->** id = 1 or name = ‘老王’ |
and | AND 嵌套 | and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))**--->** and (name = ‘李白’ and status <> ‘活着’) |
nested | 用于多条件拼接时 | nested(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))**--->** (name = ‘李白’ and status <> ‘活着’) |
apply | 用于拼接SQL语句 | 例1:apply(“id = 1”)**--->** id = 1例2:apply(“id = {0}”,1)**--->** id = 1例3:apply(“name like {0} and age > {1}”,“%J%”,18) **--->** name like ‘%J%’ and age > 18 |
last | 无视优化规则直接拼接到 sql 的最后 | last(“limit 1”) **--->** 在SQL语句最后面拼接:limit 1 |
exists | 拼接 EXISTS ( sql语句 ) | exists(“select id from table where age = 1”)**--->** exists (select id from table where age = 1) |
notExists | 拼接 NOT EXISTS ( sql语句 ) | notExists(“select id from table where age = 1”)**--->** not exists (select id from table where age = 1) |
创建Wrapper对象:
- Wrappers静态方法:
public static <T> QueryWrapper<T> query()
- 通过QueryWrapper对象的构造方法:
public QueryWrapper()
代码示例:
/\*\*
\* @author lscl
\* @version 1.0
\* @intro:
\*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo02\_Wrapper基本方法 {
@Autowired
private UserMapper userMapper;
/\*\*
\* QueryMapper的创建
\* SELECT id,name,age,email FROM user
\*/
@Test
public void test1() {
// 创建QueryMapper,默认情况下查询所有数据
QueryWrapper<User> wrapper = Wrappers.query();
QueryWrapper<User> wrapper2 = new QueryWrapper<>();
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
2)基本方法的使用
- 使用示例:
@Test
public void test2() {
QueryWrapper<User> wrapper = Wrappers.query();
// name ='Jack'
// wrapper.eq("name","Jack");
/\*
参数1: 是否要进行name条件的拼接
\*/
String name = "Jack";
wrapper.eq(name != null, "name", name);
// name != 'Jack'
// wrapper.ne("name","Jack");
// age > 20
// wrapper.gt("age",20);
// age < 20
// wrapper.lt("age",20);
// age=20
// wrapper.lt("age",20);
// age between 20 and 24
// wrapper.between("age",20,24);
// age not between 20 and 24
// wrapper.notBetween("age",20,24);
// name like "%J%" 自动拼接左右的%
// wrapper.like("name","J");
// name not like "%J%"
// wrapper.notLike("name","J");
// name like "%J"
// wrapper.likeLeft("name","J");
// name like 'J%'
// wrapper.likeRight("name","J");
// name is null
// wrapper.isNull("name");
// name is not null
// wrapper.isNotNull("name");
// name in ('Jack','Tom','Jone')
// wrapper.in("name","Jack","Tom","Jone");
// name not in ('Jack','Tom','Jone')
// wrapper.notIn("name","Jack","Tom","Jone");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
3)子查询
- 示例代码:
/\*\*
\* 子查询
\*/
@Test
public void test3() {
// 创建wrapper对象
QueryWrapper<User> wrapper = Wrappers.query();
// 相当于: name in (select name from user where age > 21)
wrapper.inSql("name","select name from user where age>21");
// 相当于: name not in (select name from user where age > 21)
// 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<User> 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<User> 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<User> 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<User> wrapper = Wrappers.query();
// 生成的SQL为: (age < ? AND (sex = ? OR name LIKE ?))
/\*
wrapper.lt("age", 20);
wrapper.and(new Consumer<QueryWrapper<User>>() {
@Override
public void accept(QueryWrapper<User> 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<User> 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<User> 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<User> 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<User> 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<User> 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<User> 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<User> 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<User> 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<User> 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查询分页
使用步骤:
- 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<User> {
}
- 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查询出结果,修改为entityboolean 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<User> 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的主键策略帮我们自动生成主键
在枚举类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代码中我们通常都需要做类似于如下的判断:
<span>${sex==0?'男':'女'}</span>
<if test=${status==0}>未开始</if>
<if test=${status==1}>进行中</if>
<if test=${status==2}>已结束</if>
if(status=0){
// 未开始....
}else if(status==1){
// 进行中
} else if(status==2){
已结束
}
以上代码比较繁琐,并且可读性不是很高;
在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');
本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。
最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。
最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。
![](https://img-blog.csdnimg.cn/img_convert/311903982dea1d8a5d2c98fc271b5b41.jpeg)
### 学习路线图
其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。
相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。
![](https://img-blog.csdnimg.cn/img_convert/1ddfaf7dc5879b1120e31fafa1ad4dc7.jpeg)
#### 网络安全工具箱
当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份**我自己整理的网络安全入门工具以及使用教程和实战。**
![](https://img-blog.csdnimg.cn/img_convert/bcd1787ce996787388468bb227d8f959.jpeg)
#### 项目实战
最后就是项目实战,这里带来的是**SRC资料&HW资料**,毕竟实战是检验真理的唯一标准嘛~
![](https://img-blog.csdnimg.cn/img_convert/35fc46df24091ce3c9a5032a9919b755.jpeg)
#### 面试题
归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**