黑马课程
MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率
开发方式
和MyBatis进行比较
在选择依赖处,勾选Web/Spring Web
和SQL/MySQL Driver
SQL/MyBatis Framework
,这是因为MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加pom.xml中依赖如下
org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test com.baomidou mybatis-plus-boot-starter 3.4.1 com.alibaba druid 1.1.16
mybatis-plus-boot-starter
就导入了mybatis相关坐标,以及spring整合mybatis相关坐标删除原有的application.properties,新建application.yml
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSources
在之前的项目中,数据库表的表名没有要求
在MyBatisPlus中,数据库表名一定要和domain中的实体类类名相对应!
例如:实体类名为Book
,那么数据库表名就应该是book
id类型应当为 bigint(20)
alter table book modify id bigint(20);
对应的Book实体类的id也应该是 Long
类型
package com.example.dao;@Mapper
public interface BookDao extends BaseMapper {
}
BaseMapper
,无需写SQL注解package com.example;@SpringBootTest
class MybatisplusApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid testGetAll(){List bookList = bookDao.selectList(null);System.out.println(bookList);}
}
package com.example.dao;@Mapper
public interface BookDao extends BaseMapper {
}
package com.example;@SpringBootTest
class MybatisplusApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid testSave(){Book book = new Book();book.setType("kk");book.setName("kk");book.setDescription("kk");bookDao.insert(book);}@Testvoid testDelete(){bookDao.deleteById(1619646956185972738L);}@Testvoid testUpdate(){Book book = new Book();book.setId(1L);book.setType("kk2");bookDao.updateById(book);}@Testvoid testGetById(){Book book = bookDao.selectById(1L);System.out.println(book);}@Testvoid testGetAll(){List bookList = bookDao.selectList(null);System.out.println(bookList);}}
一个Java类库,提供了一组注解,简化POJO实体类开发
步骤1:导入jar包
org.projectlombok lombok 1.18.12
报错的话,加上版本号;否则不加
步骤2:在实体类上加上注解
在之前都是需要手动添加getter, setter, toString等方法,现在可以用以下注解替代
package com.example.domain;@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//自动生成hashCode
public class Book {private Long id;private String name;private String type;private String description;
}
更进一步,可以简化成
package com.example.domain;@Data
public class Book {private Long id;private String name;private String type;private String description;
}
@Data
包含除构造器外的其他方法步骤1:编写拦截器
拦截器MP已经提供,只需要将其配置成Spring管理的bean对象即可
package com.example.config;@Configuration
public class MpConfig {@Beanpublic MybatisPlusInterceptor mpInterceptor(){//1. 定义MP拦截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2. 添加具体的拦截器mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mpInterceptor;}
}
步骤2:分页查询
分页查询使用的方法是:
IPage selectPage(IPage page, Wrapper queryWrapper)
@Test
void testGetByPage(){IPage page = new Page(1, 5);//查询第1页,每页10行bookDao.selectPage(page, null);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());
}
在application.yml
中增加
#开启MP的日志(输出到控制台)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
此时有很多日志输出,但许多暂时用不到,需要将其删除,此时可以新建一个 logback.xml
,如下
一些日志已经不再显示,现在取消MybatisPlus启动banner图标,在application.yml
中增加内容如下
#开启MP的日志(输出到控制台)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:banner: false
还剩下springboot的banner,如果需要将其删除,在 application.yml
中配置如下
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?useSSL=false&serverTimeZone=UTCusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSourcemain:banner-mode: off#开启MP的日志(输出到控制台)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:banner: false
bookDao.selectList(null)
查看selectList源码,发现它需要一个Wrapper
的参数
查看wrapper
,发现其为一个抽象类
public abstract class Wrapper implements ISqlSegment {...}
ctrl+h 查看其继承类
其中 QueryWrapper
即是需要的实现类
//查询 id<6 的数据
@Test
void testGetAll(){//方式一:按条件查询QueryWrapper qw = new QueryWrapper();qw.lt("id", 6);//方式二:lambda格式按条件查询QueryWrapper qw = new QueryWrapper();qw.lambda().lt(Book::getId, 6);//方式三(推荐):lambda格式按条件查询LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.lt(Book::getId, 6);List bookList = bookDao.selectList(lqw);System.out.println(bookList);
}
and 查询
//查询 id>6 但 <10的数据
lqw.gt(Book::getId, 6).lt(Book::getId, 10);
或者
lqw.gt(Book::getId, 6);
lqw.lt(Book::getId, 10);
or 查询
//查询 id<3 或者 id>10的数据
lqw.lt(Book::getId, 3).or().gt(Book::getId, 10);
//模拟页面传递过来的查询数据:查询 5 lqw = new LambdaQueryWrapper();
//null判断:先判定是否为null,如果为null,就不连接
lqw.lt(null != idMax, Book::getId, idMax);
lqw.gt(null != idMin, Book::getId, idMin);List bookList = bookDao.selectList(lqw);
System.out.println(bookList);
查询指定字段
//查询投影:只适应lambda的格式
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.select(Book::getId, Book::getName, Book::getType);
List bookList = bookDao.selectList(lqw);//查询投影:非lambda的格式
QueryWrapper qw = new QueryWrapper();
qw.select("id", "name", "type");
List bookList = bookDao.selectList(qw);
聚合查询
完成count、max、min、avg、sum的使用
@Test
void testGetAll(){QueryWrapper qw = new QueryWrapper();//1. 计算总记录数qw.select("count(*) as count, type");//2. 按type分组qw.groupBy("type");//3. 获取数据List
当MyBatisPlus无法处理需要的SQL要求时,仍然可以在bookDao中按老方法进行编写
其他条件查询使用方法可以去官网的指南上查询:https://baomidou.com/
准备:user数据库表,User类等
数据库表
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,'玛丽','123456',15,'4006184000');
User实体类
package com.example.domain;@Data
public class User {private Long id;private String name;private String password;private int age;private String tel;
}
UserDao
package com.example.dao;@Mapper
public interface UserDao extends BaseMapper {
}
需求:根据用户名和密码查询用户信息
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.eq(User::getName, "玛丽").eq(User::getPassword, "123456");//实际中密码需要md5加密
User user = userDao.selectOne(lqw);
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.between(User::getAge, 10, 50);
List userList = userDao.selectList(lqw);
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.like(User::getName, "J");//匹配 %J%
lqw.likeLeft(User::getName, "J");//匹配 %J
lqw.likeRight(User::getName, "J");//匹配 J%
List userList = userDao.selectList(lqw);
需求:查询所有数据,然后按照id降序
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
/*** condition :条件,返回boolean,当condition为true,进行排序,如果为false,则不排序* isAsc:是否为升序,true为升序,false为降序* columns:需要操作的列* 下面表示,进行排序,升序,排序字段是age*/
lqw.orderBy(true, true, User::getAge);
List userList = userDao.selectList(lqw);
准备:将数据库表user修改为 tbl_user,以进行演示
alter table user rename to tbl_user;
此时数据库表名为tbl_user,实体类为User,MP将不能正确识别
只需要在User实体类
上添加@TableName
注解即可
@Data
@TableName("tbl_user")
public class User {private Long id;private String name;private String password;private int age;private String tel;
}
如果希望对所有实体类都进行这样的替换,可以用全局配置替换@TableName
#替换所有实体类对应的表名
mybatis-plus:global-config:db-config:table-prefix: tbl_user
准备:将数据库表tbl_user中的字段password改为pwd
alter table user change password pwd varchar(32) not null;
只需要在User实体类对应字段
加上@TableField
注解即可
此时数据库表名为tbl_user,实体类为User,MP将不能正确识别
@Data
public class User {private Long id;private String name;@TableField(value = "pwd")private String password;private int age;private String tel;
}
例如:在插入数据时,User实体类中有一个online属性,但数据库表user中没有这个字段,此时将报错,解决方法如下
@TableField(exist = false)
@Data
public class User {private Long id;private String name;private String password;private int age;private String tel;@TableField(exist = false)private Integer online;
}
password将不参与查询,需要隐藏起来
@Data
public class User {private Long id;private String name;@TableField(value = "pwd", select = false)private String password;private int age;private String tel;
}
这样设置后,查询后password字段将为空
@Data
public class User {@TableId(type = IdType.AUTO)private Long id;
}
如果希望设置所有实体类的id生成策略,需要在application.yml文件中配置
#设置id生成策略
mybatis-plus:global-config:db-config:id-type: assign_id
IdType有以下几种
Auto
:数据库id自增NONE
:不设置id生成策略INPUT
:用户手工输入idASSIGN_ID(默认)
:雪花算法生成id(可兼容数值型与字符串型)ASSIGN_UUID
:以UUID生成算法作为id生成策略雪花算法
生成一个64位的二进制数字,及Long的大小
1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0
41bit-时间戳,用来记录时间戳,毫秒级
10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID,其取值范围0-31
低位5bit是工作节点ID,其取值范围0-31,两个组合起来最多可以容纳1024个节点
序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID
deleteBatchIds
List list = new ArrayList<>();
list.add(1619725419349172225L);
list.add(6L);
list.add(5L);
userDao.deleteBatchIds(list);
同理,也可以多数据查询:selectBatchIds
场景
人员表:删除了人员zhangsan
订单表:zhangsan对应的订单order1,order2成为脏数据,或者跟着一起被删除,因此无法被读取
结果:之后年度汇总,发现订单数量对不上
解决方案:逻辑标记
逻辑删除
为数据设置是否可用状态字段 deleted,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作
@TableLogic
注解 标注 删除标识字段select
语句,因此此后在查询时会自动添加如 where selected=0
步骤1:为数据库表增加逻辑删除字段 deleted,设置默认值为0,未删除
alter table user add deleted int default 0;
步骤2:在实体类User中增加删除标识字段deleted
@Data
public class User {private Long id;private String name;@TableField(select = false)private String password;private int age;private String tel;//增加删除字段:value表示未删除,delval表示已删除@TableLogic(value = "0", delval = "1")private Integer deleted;
}
步骤3:运行删除方法
userDao.deleteById(1L);
发现执行的是update语句,且数据库该条数据仍存在
在 User实体类 中添加删除字段
private Integer deleted;
在 application.yml 中配置
#配置删除字段
mybatis-plus:global-config:db-config:logic-delete-field: deletedlogic-not-delete-value: 0logic-delete-value: 1
这里的方案适用于小系统,并发在2000以下
步骤1:在数据库中增加列version,标记当前版本号
alter table user add version int default 1;
步骤2:在实体类中添加字段 version,并添加注解@Version
package com.example.domain;@Data
public class User {private Long id;private String name;@TableField(select = false)private String password;private int age;private String tel;//增加删除字段:value表示未删除,delval表示已删除@TableLogic(value = "0", delval = "1")private Integer deleted;//增加版本字段@Versionprivate Integer version;
}
步骤3:添加乐观锁的拦截器
package com.example.config;@Configuration
public class MpConfig {@Beanpublic MybatisPlusInterceptor mpInterceptor(){//1. 定义MP拦截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2. 添加之前的分页拦截器mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());//3. 添加乐观锁拦截器//作用:修改时,将在SQL语句中添加 set version=version+1mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mpInterceptor;}
}
步骤4:测试
//1. 通过要修改的数据id将当前的数据查询处理
User user = userDao.selectById(2L);
//2. 修改属性值
user.setName("Lili");
//3. 进行修改
userDao.updateById(user);
在执行前,表中该条数据的version是1,执行后version为2
该条数据是否被修改过
步骤5:模拟并发情况
@Test
void testUpdate(){//1. user1和user2同时访问第1条数据User user1 = userDao.selectById(4L); //version = 1;User user2 = userDao.selectById(4L); //version = 1;//2. user2对第1条数据进行了修改user2.setName("user2");userDao.updateById(user2); //version = 2;//3. user1此时再对第1条数据进行修改:失败user1.setName("user1");userDao.updateById(user1);
}
user1更新失败,因为它的version=1,而数据库该条数据已经变为 version=2
此时,user1需要重新操作
:再获取一次数据,然后再更新
像是BookDao,UserDao,这些类都有相似的结构,可以抽取出来做成模板,这也就是代码生成器的原理
org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test com.baomidou mybatis-plus-boot-starter 3.4.1 com.alibaba druid 1.1.16 org.projectlombok lombok 1.18.12 com.baomidou mybatis-plus-generator 3.4.1 org.apache.velocity velocity-engine-core 2.3
package com.example;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;public class CodeGenerator {public static void main(String[] args){//1. 定义一个自动生成器对象AutoGenerator autoGenerator = new AutoGenerator();//2. 设置数据源DataSourceConfig dataSource = new DataSourceConfig();dataSource.setDriverName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/ssm_db?useSSL=false&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("123456");autoGenerator.setDataSource(dataSource);//3. 设置全局配置GlobalConfig globalConfig = new GlobalConfig();globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");//设置生成的文件的位置为当前项目的java目录下globalConfig.setOpen(false);//设置生成完毕后是否打开生成代码所在的目录globalConfig.setAuthor("unknown");//设置作者globalConfig.setFileOverride(true);//设置是否覆盖原始生成的文件globalConfig.setMapperName("%sDao");//设置数据层接口名,%s为占位符,指代模块名称,默认为 %sMapperglobalConfig.setIdType(IdType.ASSIGN_ID);//设置Id生成策略autoGenerator.setGlobalConfig(globalConfig);//4. 设置包名相关配置PackageConfig packageConfig = new PackageConfig();packageConfig.setParent("com.example");//设置生成的包名,与setOutputDir的位置不冲突,二者叠加组成完整路径packageConfig.setEntity("domain");//设置实体类包名packageConfig.setMapper("dao");//设置数据层包名autoGenerator.setPackageInfo(packageConfig);//5. 策略配置(核心)StrategyConfig strategyConfig = new StrategyConfig();
// strategyConfig.setInclude("tbl_table1", "tbl_table2");//生成指定的表(必须要存在)strategyConfig.setTablePrefix("tbl_");//去掉前缀,如:数据库表名为tbl_book,那么生成的dao就为BookDao,否则为Tbl_bookDaostrategyConfig.setRestControllerStyle(true);//设置是否启用RESTful风格strategyConfig.setEntityLombokModel(true);//设置是否启用LombokstrategyConfig.setLogicDeleteFieldName("deleted");//设置逻辑删除字段名strategyConfig.setVersionFieldName("version");//设置乐观锁字段autoGenerator.setStrategy(strategyConfig);//6. 执行生成操作autoGenerator.execute();}
}
@Mapper
注解注意:在使用代码生成器时,application.yml中的内容可以为空
但是如果要在测试中使用方法,需要在application.yml中补齐dataSource的配置
代码生成器生成的结果如上,在Test类中测试
@SpringBootTest
class MybatisplusApplicationTests {@Autowiredprivate IUserService userService;@Testvoid contextLoads(){User user = userService.getById(2L);System.out.println(user);}
}