1.环境搭建
-
依赖配置
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> </dependencies>
-
mybatisplus配置
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db? serverTimezone=UTC username: root password: 123456 mybatis-plus: global-config: db-config: table-prefix: tbl_
数据库表创建:
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');
实体类对象创建(实体类对象名要与表名对应)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
-
编写测试文件
两个注解:
-
@Mapper
保证在Dao接口上添加@Mapper
注解,并且确保Dao处在引导类所在包或其子包中这样可以保证相应的实体类被注入
-
在引导类上添加
@MapperScan
注解,其属性为所要扫描的Dao所在包这样保证了所有mapper类都被扫描到
-
-
说明:
userDao注入的时候下面有红线提示的原因是什么?
-
UserDao是一个接口,不能实例化对象
-
只有在服务器启动IOC容器初始化后,由框架创建DAO接口的代理对象来注入
-
现在服务器并未启动,所以代理对象也未创建,IDEA查找不到对应的对象注入,所以提示报红
-
一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。
-
-
整体架构
-
与mybatis的主要差别
-
你会发现我们不需要在DAO接口中编写方法和SQL语句了,只需要继承
BaseMapper
接口即可 -
mybatisplus主要用于单表操作,如果需要多表操作还需要使用mybatis
-
-
mybatisplus简介
- MP旨在成为MyBatis的最好搭档,而不是替换MyBatis,所以可以理解为MP是MyBatis的一套增强工具,它是在MyBatis的基础上进行开发的(mybatis-plus包含mybatis的jar包和mybatis-spring整合jar包)
- MP的特性:
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
- ……
2.mp基础操作
1.基础操作
-
新增
int insert (T t)
-
T:泛型,新增用来保存新增数据
-
int:返回值,新增成功后返回1,没有新增成功返回的是0
测试:
@SpringBootTest class Mybatisplus01QuickstartApplicationTests { @Autowired private UserDao userDao; @Test void testSave() { User user = new User(); user.setName("黑马程序员"); user.setPassword("itheima"); user.setAge(12); user.setTel("4006184000"); userDao.insert(user); } }
结果:
这时候就发现新增了一条数据,但是我们没有设置id,这里缺自动设置了一个长id,用到了mp默认的雪花自增策略
mp默认不能设置id,否则会爆异常
-
-
删除
int deleteById (Serializable id)
这里使用
Serializable
作为参数的原因:- MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。表明了MP既能接受String类型的"1"也能接受integer类型的1。
-
修改
int updateById(T t);
- T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值
- int:返回值,修改成功后返回1,未修改数据返回0
**说明:**修改的时候,只修改实体对象中有值的字段。实体对象中,没有值的字段保持原样
测试:
@SpringBootTest class Mybatisplus01QuickstartApplicationTests { @Autowired private UserDao userDao; @Test void testUpdate() { User user = new User(); user.setId(1L); user.setName("Tom888"); user.setPassword("tom888"); userDao.updateById(user); } }
-
根据id查询
T selectById (Serializable id)
- Serializable:参数类型,主键ID的值
- T:根据ID查询只会返回一条数据
@SpringBootTest class Mybatisplus01QuickstartApplicationTests { @Autowired private UserDao userDao; @Test void testGetById() { User user = userDao.selectById(2L); System.out.println(user); } }
-
查询所有(不加条件)
List<T> selectList(Wrapper<T> queryWrapper)
- **Wrapper:**用来构建条件查询的条件,目前我们没有可直接传为Null
- List:因为查询的是所有,所以返回的数据是一个集合
-
总结
我们所调用的方法都是来自于DAO接口继承的BaseMapper类中
2.lombok简单开发
-
配置lombok
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!--<version>1.18.12</version>--> </dependency>
**注意:**版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
-
安装lombok插件
新版本IDEA已经内置了该插件,如果删除setter和getter方法程序有报红,则需要安装插件
-
lombok注解
- @Setter:为模型类的属性提供setter方法
- @Getter:为模型类的属性提供getter方法
- @ToString:为模型类的属性提供toString方法
- @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
- @Data:是个组合注解,包含上面的注解的功能
- @NoArgsConstructor:提供一个无参构造函数
- @AllArgsConstructor:提供一个包含所有参数的构造函数
-
lombok注解使用说明
@Data不包含构造方法,但JDK会自动生成无参构造,使用全参构造要加**@AllArgsConstructor,加入带参构造时自动生成的无参构造会失效所以还需要加上@NoArgsConstructor**
-
举例说明
这是一个比较完整的使用lombok注解的例子
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private String password; private Integer age; private String tel; public User(String name, String password) { this.name = name; this.password = password; } }
3.分页查询
分页查询中我们仍然需要用到一个特殊的IPage类,不同于mybatis中用到的pageHelper
介绍:
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage:用来构建分页查询条件
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page
。
使用方法:
-
配置mp分页拦截器
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //1 创建MybatisPlusInterceptor拦截器对象 MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor(); //2 添加分页拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; } }
-
- 创建Page对象,
设置分页条件
- 调用basemapper中的
selectPage(IPage,Query)
方法设置page对象和查询条件 - 方法查询后的数据会封装回page对象
- 创建Page对象,
举例:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//分页查询
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
IPage<User> page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());//重要
}
}
分页查询原理:
select * from * limit ?,?
①先查询有多少条数据
②根据分页大小得到多少页
③根据当前页数和分页大小,得到limt的两个问号的参数
- 举例:
IPage<User> page = new Page<>(2,2); //创建分页对象
//当前页2页,每页2条数据
- 日志打印:
4.测试日志
- 如果想查看MP执行的SQL语句,可以修改application.yml配置文件
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
-
取消spring的初始打印
测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:
-
取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> </configuration>
-
取消MybatisPlus启动banner图标
# mybatis-plus日志控制台输出 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: off # 关闭mybatisplus启动图标
-
取消SpringBoot的log打印
spring: main: banner-mode: off # 关闭SpringBoot启动图标(banner)
取消完之后的控制台打印效果:
-
3.DQL编程
本质上就是通过mybatis-plus实现Wrapper接口,然后按条件进行查询,这样做简化了mybatis操作的直接查询SQL语句的编写,用编程实现了查询SQL
查询操作:
封装一个wrapper对象,指定相应的查询条件
调用dao层提供的通用的带有
wrapper接口参数
的select方法进行查询mybatis字段匹配原则:
实体类属性可以比数据表中字段少,但是一个实体类对应一张表。
因此只要出现在实体类中的属性必须与数据表中的某一个字段对应,否则会出错。
如果某属性是实体类的冗余属性(即表中没有字段对应),那么用@TableField注解的exists属性设置为false声明
1.条件查询
-
介绍
- MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个
Wrapper
类,这个类就是用来构建查询条件的,如下图所示: -
构建条件查询
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式
-
QueryWrapper
直接调用方法,拼接String字段,容易出现字段不匹配,不建议使用
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ QueryWrapper qw = new QueryWrapper(); //拼接条件字段 qw.lt("age",18); List<User> userList = userDao.selectList(qw); System.out.println(userList); } }
-
LambdaQueryWrapper
用lambda表达式,方法引用实体类的字段(只要实体类与表中字段一致[后面会讲到映射匹配问题,解决映射问题]),做到查询条件拼接的准确性
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.lt(User::getAge, 10); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
-
-
多条件构建查询:
①直接queryWrapper一次一次的添加
②链式编程,不断的.
举例:
//需求:查询数据库表中,年龄小于10或年龄大于30的数据 @SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //条件 lqw.lt(User::getAge, 10).or().gt(User::getAge, 30); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
说明:
or()就相当于我们sql语句中的
or
关键字,不加默认是and
,最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
-
null值的判定:
-
问题出现:虽然构造好了查询条件,但是并不是每次查询所有的限制条件都要用到。这种情况类似mybatis的动态SQL拼接问题
-
举例:
需求:查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
-
-
解决前端与后端实体类不匹配问题:
后台如果想接收前端的两个数据,该如何接收?
**方法:**新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。让其充当保存查询条件功能
总结:
//lambda表达式泛型类一定是User,对应一张表
//UserQuery只是作为查询条件存在@Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; } //查询类 @Data public class UserQuery extends User { private Integer age2; }
-
解决动态SQL拼接问题:
使用mp重载的方法解决
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ //模拟页面传递过来的查询数据 UserQuery uq = new UserQuery(); uq.setAge(10); uq.setAge2(30); //lambda表达式泛型类一定是User,对应一张表 //UserQuery只是作为查询条件存在 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //添加条件 lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2()); lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge()); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
2.查询投影
介绍:就是从数据库查询出结果后,如何封装数据集
实现结果封装:
有实体类与其对应:通过warpper的select指定select (内容)设置
无实体类对应:
selectMaps
将查询出来的结果封装到一个list的map里面,适合查询特殊的无法封装到实体类对象的东西,如count(*)
-
查询指定字段:
-
默认:目前我们在查询数据的时候,什么都没有做
默认就是查询表中所有字段的内容
,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。 -
指定:要想实现指定查询,要调用
Wrapper.select()
方法来实现举例:
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.select(User::getId,User::getName,User::getAge); List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
最终的sql语句为:SELECT id,name,age,tel FROM user
-
-
聚合查询
-
**查询条件:**因为不是固定的列,只能用普通的select来实现,
不能用lambda形式的querywrapper
,相比mybatis在封装数据集就麻烦了 -
**返回值:**查询结果的返回值,也只能用Map集合来接收,调用
selectMaps()
方法 -
**起别名:**为了在做结果封装的时候能够更简单,我们将聚合函数后得到的结果都起了个名称,方面后期来获取这些数据
举例:
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("avg(age) as avgAge"); //返回值也只能用Map接受,要调用selectMaps()方法 List<Map<String, Object>> userList = userDao.selectMaps(lqw); System.out.println(userList); } }
-
-
分组查询:
- 聚合与分组查询,无法使用lambda表达式来完成
- MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("count(*) as count,tel"); //分组查询 lqw.groupBy("tel"); // List<Map<String, Object>> list = userDao.selectMaps(lqw); System.out.println(list); } }
3.查询条件
MP的查询条件有很多:
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
- ……
查询条件官方文档参考
-
等值查询
-
eq(): 相当于
=
,对应的sql语句为SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
-
selectList:查询结果为多个或者单个
-
selectOne:查询结果为单个
//需求:根据用户名和密码查询用户信息 @SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry"); User loginUser = userDao.selectOne(lqw); System.out.println(loginUser); } }
-
-
范围查询
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
//需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询 @SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.between(User::getAge, 10, 30); //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?) List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
-
模糊查询⭐️
- like():前后加百分号,如 %J%
- likeLeft():前面加百分号,如 %J
- likeRight():后面加百分号,如 J%
//需求:查询表中name属性的值以`J`开头的用户信息,使用like进行模糊查询 @SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.likeRight(User::getName, "J"); //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?) List<User> userList = userDao.selectList(lqw); System.out.println(userList); } }
-
排序查询
- orderBy排序
- condition:条件,true则添加排序,false则不添加排序
- isAsc:是否为升序,true升序,false降序
- columns:排序字段,可以有多个
- orderByAsc/Desc(单个column):按照指定字段进行升序/降序
- orderByAsc/Desc(多个column):按照多个字段进行升序/降序
- orderByAsc/Desc
- condition:条件,true添加排序,false不添加排序
- 多个columns:按照多个字段进行排序
//需求:查询所有数据,然后按照id降序 @SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>(); /** * condition :条件,返回boolean, 当condition为true,进行排序,如果为false,则不排序 * isAsc:是否为升序,true为升序,false为降序 * columns:需要操作的列 */ lwq.orderBy(true,false, User::getId); userDao.selectList(lw } }
- orderBy排序
4.映射匹配兼容性✏️
处理属性兼容性
:返回值相当于mybatis里面的ResultMap
,用来处理java实体类字段与表中字段不匹配的问题
-
介绍:
-
问题一:表字段与编码属性设计不同步
-
问题二:编码中添加了数据库中未定义的属性
-
这一点与mybatis不同,mybatis即使实体类中有数据库未定义的属性,也能根据sql语句将查询出来的结果赋值给相应的实体类属性
-
mp要求实体类对象中的字段必须和表中字段一一对应,如果不对应要加相应的配置说明
-
-
问题三:采用默认查询开放了更多的字段查看权限
-
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。
-
解决方案:是
@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。 -
这样处理后,不会再从数据库表中查出
pwd
赋值给User
实体类属性
-
下面问题设计到表名与实体类名对应问题:
-
问题四:表名与编码开发设计不同步
-
解决方案一:
@TableName
注解加载实体类对象上,指明表名 -
解决方案二:
mybatis-plus: global-config: db-config: #设置数据库表前缀 table-prefix: tbl_
-
-
注解知识点总结:
-
@TableField
名称 @TableField 类型 属性注解 位置 模型类属性定义上方 作用 设置当前属性对应的数据库表中的字段关系 相关属性 value(默认):设置数据库表字段名称
exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用
select:设置属性是否参与查询,此属性与select()映射配置不冲突 -
@TableName
名称 @TableName 类型 类注解 位置 模型类定义上方 作用 设置当前类对应于数据库表关系 相关属性 value(默认):设置数据库表名称
-
4.DML编程
1.id生成策略控制(针对insert)
- 不同的表应用不同的id生成策略
- 日志:自增(1,2,3,4,……)
- 购物订单:特殊规则(FQ23948AK3843)
- 外卖单:关联地区日期等信息(10 04 20200314 34 91)
- 关系表:可省略id
- ……
不同的业务采用的ID生成方式应该是不一样的,那么在MP中都提供了哪些主键生成策略,以及我们该如何进行选择?
在这里我们又需要用到MP的一个注解叫@TableId
@TableId
名称 | @TableId |
---|---|
类型 | 属性注解 |
位置 | 模型类中用于表示主键的属性定义上方 |
作用 | 设置当前类中主键属性的生成策略 |
相关属性 | value(默认):设置数据库表主键名称 type:设置主键属性的生成策略,值查照IdType的枚举值 |
-
举例:修改为id自增为1的自增策略
- 设置@TableId
@Data @TableName("tbl_user") public class User { //设置生成策略为AUTO @TableId(type = IdType.AUTO) private Long id; private String name; @TableField(value="pwd",select=false) private String password; private Integer age; private String tel; @TableField(exist=false) private Integer online; }
-
删除测试数据并修改自增值
-
修改自增值
因为之前生成主键ID的值比较长,会把MySQL的自动增长的值变的很大,所以需要将其调整为目前最新的id值。
-
一定要设置数据库表为主键id自增
-
-
常见ID自增策略
NONE
: 不设置id生成策略- INPUT:用户手工输入id
ASSIGN_ID
:雪花算法生成id(可兼容数值型与字符串型)ASSIGN_UUID
:以UUID生成算法作为id生成策略,生成为接收类型必须为(String)类型- 其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
-
拓展:分布式id
分布式ID是什么?
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
- 比如订单表就有可能被存储在不同的服务器上
- 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
- 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
-
常见ID自增策略介绍:
-
INPUT策略
:①实现此种策略首先修改实体类@TableId
②设置数据库表为非自增
③用户手工输入id,如果不输入id,会爆主键为null异常
-
ASSIGN_ID
:①实现此种策略首先修改实体类@TableId
②添加用户数据的时候不设置ID,如果用户输入了ID,那么以用户输入的为准
-
ASSIGN_UUID
:①实现此种策略首先修改实体类@TableId
②设置实体类接收id为String类型,SQL表中类型为varchar类型
主键类型设置为varchar,长度要大于
32
,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败③添加用户数据的时候不设置ID,如果用户输入了ID,那么以用户输入的为准
-
-
雪花算法:
-
ID生成策略对比
- NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂**(不推荐使用)**
- AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用(不推荐使用)
- ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
- ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键**(推荐使用,mp默认)**
- 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。
-
简化配置:
#id自增策略 mybatis-plus: global-config: db-config: id-type: assign_id #表前缀 mybatis-plus: global-config: db-config: table-prefix: tbl_
2.多记录操作(只针对select和delect)
为什么多记录操作没有insert和update
因为批量添加的话一条insert语句是
insert into tb_user values(?,?,?),(?,?,?).......
这样的话拼接SQL太长了
因此在这个basemapper中只有添加一条数据的方法,要想添加多条,要么自己写一个循环方法实现
要么实现Iservice接口,来使用里面的通用方法
多id删除
和多id查询
-
多记录删除
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
//需求:根据传入的id集合将数据库表中的数据删除掉。 @SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; @Test void testDelete(){ //删除指定多条数据 List<Long> list = new ArrayList<>(); list.add(1402551342481838081L); list.add(1402553134049501186L); list.add(1402553619611430913L); userDao.deleteBatchIds(list); } }
-
多记录查询
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
//需求:根据传入的ID集合查询用户信息 @SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetByIds(){ //查询指定多条数据 List<Long> list = new ArrayList<>(); list.add(1L); list.add(3L); list.add(4L); userDao.selectBatchIds(list); } }
3.逻辑删除✏️
-
逻辑删除出现背景:
接下来要讲解是删除中比较重要的一个操作,逻辑删除,先来分析下问题:
-
这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同)的表
-
员工ID为1的张业绩,总共签了三个合同,如果此时他离职了,我们需要将员工表中的数据进行删除,会执行delete操作
-
如果表在设计的时候有主外键关系,那么同时也得将合同表中的前三条数据也删除掉
-
后期要统计所签合同的总金额,就会发现对不上,原因是已经将员工1签的合同信息删除掉了
-
如果只删除员工不删除合同表数据,那么合同的员工编号对应的员工信息不存在,那么就会出现垃圾数据,就会出现无主合同,根本不知道有张业绩这个人的存在
-
所以经过分析,我们不应该将表中的数据删除掉,而是需要进行保留,但是又得把离职的人和在职的人进行区分,这样就解决了上述问题,如:
-
区分的方式,就是在员工表中添加一列数据
deleted
,如果为0说明在职员工,如果离职则将其改完1,(0和1所代表的含义是可以自定义的)
-
-
逻辑删除原理:(假设删除为1,不删除为0)
使用mp提供的
@Logic
注解表名逻辑删除字段后,用mp进行以下操作:-
每次在进行删除的时候,会将逻辑删除字段设置为1,不删除字段为0
-
每次在进行修改操作时,加上不修改逻辑删除字段(为1)的条件
-
每次在进行查询操作时,加上不修改逻辑删除字段(为1)的条件
通过上述操作,实现了逻辑删除的效果
-
-
逻辑删除功能实现
①在数据库表中加上冗余字段
②实体类添加属性
-
添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用**@TableField进行关系映射**(必须要映射上),如果一致,则会自动对应。
-
标识新增的字段为逻辑删除字段,使用
@TableLogic
@Data public class User { ...... @TableLogic(value="0",delval="1") //value为正常数据的值,delval为删除数据的值 private Integer deleted; }
-
③进行测试
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1L);
}
}
-
打破逻辑删除功能
如果还是想把已经删除的数据都查询出来该如何实现呢?
答:使用mybatis原生SQL查询
@Mapper public interface UserDao extends BaseMapper<User> { //查询所有数据包含已经被删除的数据 @Select("select * from tbl_user") public List<User> selectAll(); }
-
逻辑删除简化配置
mybatis-plus: global-config: db-config: # 逻辑删除字段名 logic-delete-field: deleted # 逻辑删除字面值:未删除为0 logic-not-delete-value: 0 # 逻辑删除字面值:删除为1 logic-delete-value: 1
4.乐观锁✏️(针对update)
- 数据库表中添加version列,比如默认值给1
- 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第一个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
- 假如第一个线程先执行更新,会把version改为2,
- 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
- 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
**总结:**说白了就是两个操作都先执行查找操作,拿到同一个版本,但是只能有一个人进行修改,因为只要有一个人修改了,版本也就跟着更新了
乐观锁注意事项:使用乐观锁更新数据,更新字段一定要带上乐观锁字段,否则无法实现
乐观锁实现步骤:
-
**数据库表添加列,给列添加上默认值。**比如说1,记录最开始的版本
-
在模型类中添加对应的属性,并加上@veision注解
-
添加乐观锁的拦截器
因为乐观锁实现要给sql语句加上额外语句,类似分页拦截器要加上limit…,属于额外功能,所以需要配置上拦截器
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加乐观锁拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } }
-
执行更新操作:
注意:在执行更新之前一定要查询一遍或者将乐观锁字段设置进更新字段里面,确保乐观锁生效
@SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; @Test void testUpdate(){ User user = new User(); user.setId(3L); user.setName("Jock666"); //设置乐观锁字段 user.setVersion(2); userDao.updateById(user); } }
乐观锁验证举例:
最终结果是数据库表中变成"Jock bbb",version从1变成2
@Test
public void testOptimisticLock(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=1
User user2 = userDao.selectById(3L); //version=1
user2.setName("Jock bbb");
userDao.updateById(user2); //version=>2
user.setName("Jock aaa");
userDao.updateById(user); //verion=2?条件还成立吗?
}
官网乐观锁
5.通用service接口
说明:
- 通用 Service CRUD 封装
IService
接口,进一步封装 CRUD 采用get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分Mapper
层避免混淆,- 泛型
T
为任意实体对象- 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类- 对象
Wrapper
为 条件构造器
使用通用service接口可以将basemapper中简单的增删改查操作逻辑化,满足业务要求
但是这个接口实现的方法都很通用,日常开发还要自己封装业务逻辑代码
官网地址
IService中的CRUD方法
-
增加:Save、SaveOrUpdate
// 插入一条记录(选择字段,策略插入) boolean save(T entity); // 插入(批量) boolean saveBatch(Collection<T> entityList); // 插入(批量) boolean saveBatch(Collection<T> entityList, int batchSize); // TableId 注解存在更新记录,否插入一条记录 boolean saveOrUpdate(T entity); // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
-
删除:Remove
// 根据 entity 条件,删除记录 boolean remove(Wrapper<T> queryWrapper); // 根据 ID 删除 boolean removeById(Serializable id); // 根据 columnMap 条件,删除记录 boolean removeByMap(Map<String, Object> columnMap); // 删除(根据ID 批量删除) boolean removeByIds(Collection<? extends Serializable> idList);
-
修改:Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset boolean update(Wrapper<T> updateWrapper); // 根据 whereWrapper 条件,更新记录 boolean update(T updateEntity, Wrapper<T> whereWrapper); // 根据 ID 选择修改 boolean updateById(T entity); // 根据ID 批量更新 boolean updateBatchById(Collection<T> entityList); // 根据ID 批量更新 boolean updateBatchById(Collection<T> entityList, int batchSize);
-
查询:Get、List、Count
// 根据 ID 查询 T getById(Serializable id); // 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1") T getOne(Wrapper<T> queryWrapper); // 根据 Wrapper,查询一条记录 T getOne(Wrapper<T> queryWrapper, boolean throwEx); // 根据 Wrapper,查询一条记录 Map<String, Object> getMap(Wrapper<T> queryWrapper); // 根据 Wrapper,查询一条记录 <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); // 查询所有 List<T> list(); // 查询列表 List<T> list(Wrapper<T> queryWrapper); // 查询(根据ID 批量查询) Collection<T> listByIds(Collection<? extends Serializable> idList); // 查询(根据 columnMap 条件) Collection<T> listByMap(Map<String, Object> columnMap); // 查询所有列表 List<Map<String, Object>> listMaps(); // 查询列表 List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper); // 查询全部记录 List<Object> listObjs(); // 查询全部记录 <V> List<V> listObjs(Function<? super Object, V> mapper); // 根据 Wrapper 条件,查询全部记录 List<Object> listObjs(Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录 <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); // 查询总记录数 int count(); // 根据 Wrapper 条件,查询总记录数 int count(Wrapper<T> queryWrapper);
-
Page
// 无条件分页查询 IPage<T> page(IPage<T> page); // 条件分页查询 IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper); // 无条件分页查询 IPage<Map<String, Object>> pageMaps(IPage<T> page); // 条件分页查询 IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
6.快速开发
使用
mybatisx
插件实现快速开发功能:
- 快速生成CRUD模版,生成mapper和service
- 对于一些需要自己编写的代码和SQL语句,比如多表联查,可以加上自己清晰表达后实现快速生成
SQL语句
插件官网
1.快速生成代码
-
新建一个Spring Boot项目引入依赖(创建工程时记得勾选lombok及mysql驱动)
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency>
-
配置数据源信息
spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false username: root password: 123456
-
在IDEA中与数据库建立链接
-
填写数据库信息并保存
-
找到我们需要生成的表点击右键
-
填写完信息以后下一步
-
继续填写信息
-
大功告成(真特么好用yyds)
2.快速生成CRUD
MyBaitsX可以根据我们在Mapper接口中输入的方法名快速帮我们生成对应的sql语句
per queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage page, Wrapper queryWrapper);
# 6.快速开发
> 使用`mybatisx`插件实现快速开发
>
> 功能:
>
> * 快速生成CRUD模版,生成mapper和service
> * 对于一些需要自己编写的代码和SQL语句,比如多表联查,可以加上自己清晰表达后实现快速生成`SQL语句`
[插件官网](https://baomidou.com/pages/ba5b24/)
## 1.快速生成代码
- 新建一个Spring Boot项目引入依赖(创建工程时记得勾选lombok及mysql驱动)
```xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
-
配置数据源信息
spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false username: root password: 123456
-
在IDEA中与数据库建立链接
-
填写数据库信息并保存
-
找到我们需要生成的表点击右键
[外链图片转存中…(img-nSTQncGU-1687610658206)]
-
填写完信息以后下一步
[外链图片转存中…(img-wZ7fmK6s-1687610658207)]
-
继续填写信息
[外链图片转存中…(img-5g9FqjKD-1687610658208)]
-
大功告成(真特么好用yyds)
[外链图片转存中…(img-QoUYwWJ9-1687610658209)]
2.快速生成CRUD
MyBaitsX可以根据我们在Mapper接口中输入的方法名快速帮我们生成对应的sql语句
[外链图片转存中…(img-6Tc55Nkg-1687610658210)]
[外链图片转存中…(img-kIO6dXPk-1687610658210)]