1、Mybatisplus
MP是基于Mybatis框架基础上开发的增强型工具,旨在简化开发、提高效率!
入门案例:
-
Springboot整合mybatis开发—复习
-
创建SpringBoot工程
-
勾选配置使用的技术
-
设置dataSource相关属性(DBC参数)
-
定义数据层接口映射配置
-
1、导入起步依赖
创建项目的时候只要web和mysql,不需要mybatis,因为导入mybatis-plus里边会自动有一些基础的包。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
2、配置yml,设置jdbc参数
spring:
datasource:
username: root
password: 123
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/user?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
3、创建entity
package com.yang.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String pwd;
private Integer age;
private String tel;
}
4、创建dao
// 定义数据接口,继承BaseMapper<User> !!!
@Mapper
public interface UserDao extends BaseMapper<User> {
}
5、测试
package com.yang;
@SpringBootTest
class Springboot12MybatisplusApplicationTests {
@Autowired
private UserDao userDao;
@Test
void contextLoads() {
List<User> users = userDao.selectList(null);
// 遍历list
for (User s : users) {
System.out.println(s);
}
}
}
2、Mybatisplus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
3、标准CRUD操作
1、标准数据层CRUD功能
2、测试
package com.yang;
@SpringBootTest
class Springboot12MybatisplusApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetById() {
User user = userDao.selectById(1L);
System.out.println(user);
}
@Test
void testGetAll() {
List<User> users = userDao.selectList(null);
users.forEach(System.out::println);
}
@Test
void testAdd(){
User user = new User();
user.setId(7L);
user.setName("POOP");
user.setPassword("123456789");
user.setAge(18);
user.setTel("115588988774");
userDao.insert(user);
}
@Test
void testUpdate(){
User user = new User();
user.setId(7L); // 修改哪个id用户的属性!
user.setName("Pool");
user.setTel("18155455695");
userDao.updateById(user);
}
@Test
void testDelete(){
userDao.deleteById(8L);
}
}
4、标准分页功能制作
分页查询 – 复习
limit start, size 的含义:start是表示起始数据的行数、size表示展示的数据数量
特别需要注意的是MySQL中的第一条数据是从下标index = 0开始算的。例如:查询工资的前1-5名,和工资的2-6名。
-- 前1-5名
select name, sal from emp order by sal desc limit 0, 5
-- 前2-6名
select name, sal from emp order by sal desc limit 1, 5
limit [start], size中的start可以不写,如果不写等价于start = 0,size 表示需要展示的数据条数。
查询第k名(升序、逆序)时聚合函数似乎显得力不从心了,这时候可以使用order by 排序 + limit 进行一下挑选。
查询某个字段第k名的数据。注意是k-1(从0开始!)
select * from 表名 order by 字段 desc/asc limit k-1, 1
Mp实现分页查询:
1、配置分页拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
// 1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
// 2.添加具体的拦截器
// 添加分页拦截器,如果不添加,则无法实现mp的分页,添加即可使用
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
2、测试
@Test
void testGetByPage(){
// 1代表你查第几页,2代表每页查几条
IPage page = new Page(1,2);
userDao.selectPage(page,null);
System.out.println("当前页码值:" + page.getCurrent()); // 1
System.out.println("每页显示数:" + page.getSize()); // 2
System.out.println("一共多少页:" + page.getPages()); // 4
System.out.println("一共多少条数据:" + page.getTotal()); // 7
System.out.println("数据:" + page.getRecords());
// 数据:[User(id=1, name=Tom, password=123, age=3, tel=16855655632),
// User(id=2, name=Jac, password=123, age=13, tel=12223232323)]
}
3、配置MybatisPlus的运行日志:(调程序用的,一般可以不开启!)
# 开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5、条件查询的三种格式
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
所有带Wrapper queryWrapper的都是可以添加条件的。
取消控制台的所有日志打印,看着更加简单! 在Resources下创建logback.xml文件。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
</configuration>
Mybatis-plus中sql语句LT、LE、EQ、NE、GE、GT的意思:
- lt:less than 小于
- le:less than or equal to 小于等于
- eq:equal to 等于
- ne:not equal to 不等于
- ge:greater than or equal to 大于等于
- gt:greater than 大于
QueryWrapper条件构造器
queryWrapper.lt()——小于
queryWrapper.le()——小于等于
queryWrapper.gt()——大于
queryWrapper.ge()——大于等于
queryWrapper.eq()——等于
queryWrapper.ne()——不等于
queryWrapper.betweeen(“age”,10,20)——age在值10到20之间
queryWrapper.notBetweeen(“age”,10,20)——age不在值10到20之间
queryWrapper.like(“属性” , “值”)——模糊查询匹配值‘%值%’
queryWrapper.notLike(“属性” , “值”)——模糊查询不匹配值‘%值%’
queryWrapper.likeLeft(“属性”,“值”)——模糊查询匹配最后一位值‘%值’
queryWrapper.likeRight(“属性”,“值”)——模糊查询匹配第一位值‘值%’
queryWrapper.isNull()——值为空或null
queryWrapper.isNotNull()——值不为空或null
queryWrapper.in(“属性”,条件,条件 )——符合多个条件的值
queryWrapper.notIn(“属性”,条件,条件 )——不符合多个条件的值
queryWrapper.or()——或者
queryWrapper.and()——和
queryWrapper.orderByAsc(“属性”)——根据属性升序排序
queryWrapper.orderByDesc(“属性”)——根据属性降序排序
queryWrapper.inSql(“sql语句”)——符合sql语句的值
queryWrapper.notSql(“sql语句”)——不符合SQL语句的值
queryWrapper.esists(“SQL语句”)——查询符合SQL语句的值
queryWrapper.notEsists(“SQL语句”)——查询不符合SQL语句的值
@Test
void testGetAll() {
// 第一种方式:按条件查询
QueryWrapper qw = new QueryWrapper();
// 添加条件年龄小于18
qw.lt("age",18);
List<User> users = userDao.selectList(qw);
users.forEach(System.out::println);
// 第二种方式:按条件查询
QueryWrapper<User> qw = new QueryWrapper<User>();
// 添加条件年龄小于18
// User::getAge,为lambda表达式中的,类名::方法名
qw.lambda().lt(User::getAge,18);
List<User> users = userDao.selectList(qw);
users.forEach(System.out::println);
// 第三种方式:按条件查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.lt(User::getAge,18);
lqw.gt(User::getAge,18);
// 上边两条代码可以写成链式编程
// lqw.lt(User::getAge,30).gt(User::getAge,18);
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
}
组合条件
并且关系(and)
//并且关系
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//并且关系:10到30岁之间
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
或者关系(or)
//或者关系
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//或者关系:小于10岁或者大于30岁
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
6、条件查询NULL判空
遇到条件查询,记得添加空值判定!!!
// 模拟页面传递过来的数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
// null判定
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.lt(null != uq.getAge2(),User::getAge,uq.getAge2());
lqw.gt(null != uq.getAge(),User::getAge,uq.getAge());
List<User> users = userDao.selectList(lqw);
users.forEach(System.out::println);
7、查询投影
通俗来讲就是设置查询出来的结果长什么样! 查询字段控制!
// LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// lqw.select(User::getId,User::getName,User::getAge);
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id","name","age","tel"); // 限制只输出这几个字段!
//qw.select("id,name,age,tel"); // 相同的效果
List<User> users = userDao.selectList(qw);
users.forEach(System.out::println);
// 统计报表(分组查询聚合函数) groupBy
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("count(*) as count , age");
// 按age分组
qw.groupBy("age");
List<Map<String, Object>> users = userDao.selectMaps(qw);
users.forEach(System.out::println);
// 输出{每个年龄的个数 + 年龄}的集合
8、查询条件设置
- 范围匹配 (>、= 、 between )
- 模糊匹配 (like)
- 空判定(null)
- 包含性匹配 (in)
- 分组(group)
- 排序(order)
- …
// 条件查询 eq
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// 相当于=
lqw.eq(User::getName,"Tom").eq(User::getPassword,"123");
User user = userDao.selectOne(lqw);
System.out.println(user);
// 条件查询 between
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// 查询年龄在18-30之间的 , 严格前边参数小,后边大
lqw.between(User::getAge,18,30);
List<User> users = userDao.selectList(lqw);
System.out.println(users);
// 条件查询 -- 查信息,搜索新闻(非全文检索版:like匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// 条件查询带有P的名字 ==> %P%
lqw.like(User::getName,"P");
// 条件查询以P开头的名字 ==> P%
lqw.likeRight(User::getName,"P")
// 条件查询以P结尾的名字 ==> %P
lqw.likeLeft(User::getName,"P");
List<User> users = userDao.selectList(lqw);
System.out.println(users);
https://baomidou.com/pages/10c804/#isnull
查看官方文档查看更多 条件构造器 的方法!!
9、映射匹配兼容性
字段映射 与 表明映射
问题一 、如果数据库字段 和 实体类中的属性名不一样时怎么办??? 一般修改代码中的属性名!!!
方法 :使用 @TableField (value=“”)
-
名称:@TableField
-
类型:属性注解
-
位置:模型类属性定义上方
-
作用:设置当前属性对应的数据库表中的字段关系
-
例如
public class User { @TableField(value="pwd") private String password; }
-
相关属性
- value(默认):设置数据库字段名
- exist: 设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用
- select:设置属性是否参与查询,此属性与select() 映射配置不冲突
问题二、实体类中添加了数据库中未定义的属性
方法:使用 @TableField(exist = false)
public class User {
@TableField(exist = false)
private Integer online;
}
问题三、采用默认查询开放了更多的字段查看权限!
比如:我们查询用户信息的时候,不能把密码查询输出
public class User {
@TableField(value="pwd", select = false)
private String password;
}
问题四、表名与编码开发设计不同步
方法 :使用 @TableName(“tb1_User”) 注解类
CREATE TABLE `tb1_user`(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32),
`pwd` varchar(32),
`age` int(3),
`tel` varchar(32),
PRIMARY KEY (`id`)
)
@TableName("tbl_user") // 相当于做了个映射关系,实体类名称和表名做了个绑定
public class User {
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;
}
10、id生成策略控制
@TableId(type = IdType.AUTO) // 自增
private Long id;
@TableId(type = IdType.INPUT) // 需要用户自行输入
private Long id;
@TableId(type = IdType.ASSIGN_ID) // 自动生成Id 如果自己设置了就是自己设置的,如果没有设置则是自动生成的
private Long id;
- AUTO(0):使用数据库id自增策略控制id生成
- NONE(1)︰不设置id生成策略
- INPUT(2)︰用户手工输入id
- ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
- ASSIGN_UUID(4):以UUID生成算法作为id生成策略
但是,这样配置每个属性上一个注解,这样显的很臃肿,可以在yml上进行设置。
mybatis-plus:
global-config:
db-config:
# id自增
id-type: auto
# 每一个实体类上就不用写@TableNmae()了,相当于拼接tb_
table-prefix: tb_
11、多数据操作(删除和查询)
@Test
void testBatch(){
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(2L);
// 按照主键删除多条记录
// userDao.deleteBatchIds(list);
// 按照主键查询多条数据
userDao.selectBatchIds(list);
}
12、逻辑删除
删除操作业务问题:业务数据从数据库中丢弃
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据仍保存在数据库中
1、 数据库添加deleted字段(默认为0),实体类添加deleted属性
// 逻辑删除字段,标记当前记录是否被删除 0没删除,1代表删除
@TableLogic(value = "0", delval = "1")
private Integer deleted;
2、 测试删除
@Test
void testDelete(){
userDao.deleteById(6L);
}
控制台:可以看出删除操作变成了更新操作!
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
> Parameters: 6(Long)
< Updates: 1
3、测试:查询
@Test
void testGetAll() {
// 条件查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
List<User> users = userDao.selectList(lqw);
System.out.println(users);
}
查询结果没有6号用户(只有deleted字段为0的)
注意:
以上注解@TableLogic(value = “0”, delval = “1”)也可以写成配置的形式
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 设置逻辑删除的字段名
logic-delete-value: 1 # 删除了的字段为1
logic-not-delete-value: 0 # 没有删除的字段为0
13、乐观锁
锁是用来解决并发问题的!(并发操作:是指同一时间可能有多个用户对同一数据进行读写操作)
MP是如何实现锁?一般只能处理2000请求量以下,只能满足小企业开发中的情形。
1、数据库添加version字段 (默认为1) ,实体类添加version属性
@Version
private Integer version;
2、添加乐观锁的拦截器
@Configuration
public class MPconfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
// 1、定义Mp拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 2、添加具体的拦截器(分页拦截器)
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
// 3、添加乐观锁的拦截器
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
3、测试
@Test
void testOptLock(){
// 1、先按id查询出要修改的用户
User user = userDao.selectById(5L);
User user2 = userDao.selectById(5L);
// 2、模拟两个用户同时修改name属性
user.setName("Jac 111");
userDao.updateById(user);
user2.setName("Jac 222");
userDao.updateById(user2);
}
前一个用户更新完操作会修改version的值为2,
第二个用户获取到version就不是1了 所以不能查询成功!
14、代码生成器 – 快速开发
- 模板:MyBatisPlus提供
- 数据库相关配置:读取数据库获取信息
- 开发者自定义配置:手工配置
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generate</artifactId>
<version>2.3</version>
</dependency>
<!--velocity模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
package com.yang;
import com.baomidou.mybatisplus.enums.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;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class Generator {
public static void main(String[] args) {
// 1、创建代码生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
// 2、数据源配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("123");
autoGenerator.setDataSource(dataSource);
// 3、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("yang"); //设置作者
gc.setOpen(true); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER); //主键策略
autoGenerator.setGlobalConfig(gc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.generator"); // 设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
autoGenerator.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
//strategy.setInclude("user","teacher"); // 设置当前参与生成的表名,参数为可变参数,可以多个!
//strategy.setTablePrefix("tbl_"); // 生成实体时去掉表前缀
//strategy.setNaming(NamingStrategy.underline_to_camel); // 数据库表映射到实体的命名策略
//strategy.setColumnNaming(NamingStrategy.underline_to_camel); //数据库表字段映射到实体的命名策略
strategy.setRestControllerStyle(true); // restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); // url中驼峰转连字符
strategy.setEntityLombokModel(true); // 设置entity是否启用Lombok
//strategy.setLogicDeleteFieldName("deleted"); // 设置逻辑删除字段名
//strategy.setVersionFieldName("version"); // 设置乐观锁字段名
autoGenerator.setStrategy(strategy);
// 6、执行
autoGenerator.execute();
}
}