Mybatis-Plus 从不会到熟练使用
导航:
一. MyBaits-Plus简介及快速入门
1.1学习前的技术储备
- 熟悉Lambda表达式
- 熟悉SpringBoot、Maven
- 最好熟悉MyBatis
1.2Mybatis vs JPA
-
MyBatis的优势:
- SQL语句可以自由控制,更灵活,性能更高
- SQL与代码分离,易于阅读和维护
- 提供XML标签,支持编写动态SQL语句
-
JPA的优势:
- JPA移植性比较好(JPQL)
- 提供了很多CRUD方法、开发效率高
- 对象化程度更高
-
MyBatis的劣势:
- 简单的CRUD操作还得写SQL语句
- XML中有大量的SQL需要维护
- MyBaits自身功能有限,但支持Plugin
MP: Mybatis-Plus的出现就是为了解决MyBatis的缺点又能继承它的优点来的;
1.3 MyBatis-Plus的简介
- MP是一个MyBatis的增强工具,只做增强不做改变
- 文档及项目地址: 直接百度即可
1.4 特性介绍
- 无侵入、损耗小、强大的CRUD操作
- 支持Lambda形式调用,支持多种数据库
- 支持主键自动生成,支持ActiveRecord模式
- 支持自定义全局通用操作,支持关键词自动转义
- 内置代码生成器、内置分页插件、内置性能分析插件
- 内置全局拦截插件、内置SQL注入剥离器
1.5 快速入门
- 步骤:
- 过程:
-
创建名字为mp 的数据库
-
放入数据: user 表,图示:
-
初始化数据:
-
建好表并存入数据,然后创建项目:
- groupId: com.mp
- ArtiafctId: first
- 引入依赖:
<dependency> <groupId>org.springframework.boot</groupI> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupI> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupI> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.baomidou</groupI> <artifactId>mybaits-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>mysql</groupI> <artifactId>mysql-connector-java</artifactId> </dependency>
- 在resource的目录下创建application.yml:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useSSL=false&Timezone=GMT%2B8 username: root password: root
- 创建启动类: com.mp.Application
@SpringBootApplication @MapperScan("com.mp.dao") public class Application{ SpringApplication.run(Application.class,args); }
- 创建实体类User:com.mp.entity.User
@Data @ToString public class User{ //主键 private Long id; //姓名 private String name; //年龄 private Integer age; //邮箱 private String email; //直属上级 private Long managerId; //创建时间 private LocalDateTime createTime; }
- 创建dao层接口UserMapper: com.mp.dao.UserMapper
public interface UserMapper extends BaseMapper<User>
- 创建测试类:SimpleTest com.mp.SimpleTest;
@RunWith(SpringRunner.class) @SpringBootTest public class SimpleTest{ @Autowried private UserMapper userMapper; @Test public void select(){ List<User>list=userMapper.selectList(null); Assert.assertEquals(5,list.size()); list.forEach(System.out::println); } }
1.6 分析:
-
SSM传统编程模式:
- 接口中写抽象方法
- XML或注解写SQL
- Service中调用接口
- Controller中调用
-
通用Mapper
- 写代码前对application.yml的操作:
logging: level: root: warn com.mp.dao: trace pattern: console: '%p%m%n'
- 新增方法:创建测试类:InsertTest.java
@RunWith(SpringRunner.class) @SpringBootTest public class InsertTest{ @Autowried private UserMapper userMapper; @Test public void insert(){ User user=new User(); user.setName("刘明强"); user.setAge(31); user.setManagerId(1088248166370832385L) user.setCreateTime(LocalDateTime.now()); int rows.userMapper.insert(user); System.out.println("影响记录数"+rows); }
1.7 常用注解:
- @TableName(“数据库表名”): 用来表示此Entity下的类与数据库表名的对应关系
- @TableId(“数据库主键Id名字”) 如果是userId与user_id之间的关系则不需要给value,它是放在在主键上面的
- @TableField: 它是放在其他字段上面的
如果属性名与数据库字段一致则无需修改,如果不一致则在括号中给对应的数据库字段名,如下所示:
//加上注解的改变(已在数据库中,将该表名改为mp_user,字段id改为user_id)
@Data
@TableName("mp_user")
public class User{
//主键
@TableId
private Long userId;
@TableField("name");
private String realName;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
1.7 排除非表字段的三种方式
- 概述: 有些字段我们在数据库中不想保存此记录,则可以进行忽略,则此属性的值不会被保存;
- 方式:
- 方式一: 加上transient关键词:(为null)
- 如: private transient String 属性名; (不参与序列化)
- 方式二: 使用静态变量static : (全类唯一一份,不符合要求)
- 如: private static String 属性名 并设置静态的get,set方法
- 方式三: 使用@TableField(exist=false): (每个对象有自己单独的一份)
@TableField(exist=false); private String 属性名;
- 方式一: 加上transient关键词:(为null)
二. 增删查改
2.1 查询
- 前面为了展示注解的作用,对数据库和对应的实体类进行了修改,这里假设已经还原;
- 结构:
- 基本查询方法
- 以条件构造器为参数的查询方法
- select 中字段不全出现的处理方法
2.2 查询演示:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RetrieveTest{
@Autowired
private UserMapper userMapper;
/**
* 根据Id查询
* /
@Test
public void selectById(){
User user=userMapper.selectById(1094590409767661570L);
System.out.println(user);
}
/**
* 根据Id集合查询
* /
@Test
public void selectIds(){
List<Long> idsList=Arrays.asList(10945920410xxx,xxxxx,xxxxx,xxxx);//获取id集合
List<User> userList=userMapper.selectBatchIds(idsList);
userList.forEach(System.out::println);
}
/**
*根据条件查询
*/
@Test
public void selectByMap(){
Map<String,Object> columnMap=new HashMap<>();
// columnMap.put("name","王天风"); //必须与数据库中的对应,如果没有会报错
columnMap.put("age",27); //键是数据库中的列 where age= 27
List<User> userList=userMapper.selectByMap(columnMap);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求: 名字中包含雨并且年龄小于40
* name like '%雨%' and age<40
*/
@Test
public void selectByWrapper1(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.like("name","雨").It("age",40);
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求2: 名字中包含雨并且年龄大于等于20且小于等于40并且email不为空
* name like '%雨%' and age between 20 and 40 and email is not null
*/
@Test
public void selectByWrapper2(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求3: 名字为王姓或者年龄大于等于25,按照年龄降序排列,年龄相同按照id升序排列;
* name like '王%' or age>= 25 order by age desc,id asc
*/
@Test
public void selectByWrapper3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.likeRight("name","王").or().ge("age",25).orderByDesc("age").orderByAsc("id");
List<User> userList= userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求4: 创建日期为2019年2月14日并且直属上级为名字为王姓
* data_format(craete_time,'$Y-%m-%d')and manager_id in (select id from user where name like '王%')
*/
@Test
public void selectByWrapper4(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.apply("date_format(create_time,'%Y-%m-%d')=2019-02-14").inSql("manager_id","select id from user where name like '王%'");
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求5: 名字为王姓并且(年龄小于40或邮箱不为空)
* name like '王%' and (age<40 or email is not null)
*/
@Test
public void selectByWrapper5(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.likeRight("name","王").and(wq->wq.It("age",40).or().isNotNull("email"));
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求6: 名字为王姓或者(年龄小于40并且年龄大于20并且邮箱不为空)
* name like '王%' or (age<40 and age>20 and email is not null)
*/
@Test
public void selectByWrapper5(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.likeRight("name","王").or(wq->wq.It("age",40).gt("age",20).isNotNull("email"));
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求7: (年龄小于40或邮箱不为空)并且名字为王姓
* (age<40 or email is not null) and name like '王%'
*/
@Test
public void selectByWrapper7(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.nested(wq.It("age",40).or().isNotNull("email")).likeRight("name","王");
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求8: 年龄为30、31、34、35
* age in (30、31、34、35)
*/
@Test
public void selectByWrapper8(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.in("age",Arrays.asList(30,31,34,35));
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求9:只返回满足条件的其中一条语句即可
* limit 1
*/
@Test
public void selectByWrapper9(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.in("age",Arrays.asList(30,31,34,35)).last("limit 1");
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求10: 名字中包含雨并且年龄小于40 只查询id,name两个字段
* name like '%雨%' and age<40
*/
@Test
public void selectByWrapper10(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.select("id","name").like("name","雨").It("age",40);
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
/**
* 条件构造器查询
* 需求10: 名字中包含雨并且年龄小于40 只查询部分字段使用排除法
* select id,name,age,email from user where like '%雨%' and age<40
*/
@Test
public void selectByWrapper10(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.like("name","雨").It("age",40).select(User.class,info->!info.getColumn().equals("create_time")&&!info.getColumn().equals("manager_id"));
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
condition作用:当它的值为true的时候,这个方法才会执行
@Test
public void testCondition(){
String name="王";
String email="";
condition(name,email);
}
private void condition(String name,String email){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
//if(StringUtils.isNotEmpty(name)){
//queryWrapper.like("name",name);
//}
//if(StringUtils.isNotEmpty(email)){
// queryWrapper.like("email",email);
//}
// 下面的写法可以替代上面的这两个
queryWrapper.like(StringUtils.isNotEmpty(name),"name",name).like(StringUtils.isNotEmpty(email),"email",email);
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
实体作为条件构造器构造方法的参数
@Test
public void selectByWrapperEntity(){
User whereUser=new User();
whereUser.setName("刘红雨");
whereUser.setAge(32);
QueryWrapper<User> queryWrapper=new QueryWrapper<User>(whereUser);
queryWrapper.like("name","雨").It("age",40); //这条语句写上,会与whereUser这条同时生效;
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
- 当加上了SqlCondition.like,那么name字段的内容就与queryWrapper.like(“name”,“雨”)意思一致;它里面还有很多种,有空自己可以去看看;
2.3 AllEq用法
@Test
public void selectByWrapperAllEq(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
Map<String,Object> params=new HashMap<String,Object>();
params.put("name","王天风");
params.put("age",25);
//queryWrapper.allEq(params);
//过滤查询
queryWrapper.allEq((k,v)!k.equals("name"),params);
List<User> userList=userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
这里allEq就可以加搜索条件
2.4 其他使用条件构造器的方法
- 核心代码
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.select("id","name").like("name","雨").It("age",40);
//使用selectMaps,返回的结果是键值对,即Key为字段名,value为值名;
//使用了select指定了id,name,则返回的只有这两个数据;如果非selectMaps方式,则返回的结果,非id,name的字段会为null,而现在的没有此字段,更好看一点;
List<Map<String.Object>> userList=userMapper.selectMaps(queryWrapper);
userList.forEach(System.out::println);
-
示例(图示):
在这里插入图片描述
-
selectObjs :只返回所有的第一列数据(第一个字段的值)
-
selectCount : 查询总记录数 这里的就是count(1)
-
selectOne: 查询返回一条信息(如果有多条会报错,只能容忍一条或者0条数据)
2.4 Lambda条件过滤器
//使用Lambda来写,会编译时检查User中的字段名是否正确
@Test
public void selectLambda(){
//lambda的创建方式有三种,如下所示:
//LambdaQueryWrapper<User> lambda =new QueryWrapper<User>().lambda;
//LambdaQueryWrapper<User> lambdaQueryWrapper=new LambdaQueryWrapper<User>();
//第三种:
LambdaQueryWrapper<User> lambdaQuery=Wrappers.<User> lambdaQuery();
lambdaQuery.like(User::getName,"雨").It(User::getAge,40);
//where name like '%雨%'
List<User> userList=userMapper.selectList(lambdaQuery);
userList.forEach(System.out::println);
}
-
具体应用:
-
还有一种Lambda的创建方式
@Test
public void selectLambda3(){
List<User> userList=new LambdaQueryChainWrapper<User>(userMapper)
.like(User::getName,"雨").ge)(User::getAge,20).list();
userList.forEach(System.out::println);
}
2.5 使用条件构造器的自定义SQL 自定义方法
- 版本必须大于等于3.0.7
- 演示:
- 在Mapper中进行修改 src/main/java/com/mp/dao/userMapper
public interface UserMapper extends BaseMapper<User>{
//使用SQL语句自定义条件 的方法
@Select("select * from user ${ew.customSqlSegment}")
List<User> selectAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}
- 对上面写的方法进行测试使用:
@Test
public void selectMy(){
LambdaQueryWrapper<User> lambdaQuery =Wrappers<User> lambdaQuery();
lambdaQuery.likeRight(User::getName,"王")
.and(lqw->lqw.It(User::getAge,40).or().isNotNull(User::getEmail));
List<User> userList=userMapper.selectAll(lambdaQuery);
userList.forEach(System.out::println);
}
生成的语句如下:
2.6 另外一种方式,写入XML的方式
- 在application.yml添加数据:
mybatis-plus:
mapper-locations:
- com/mp/mapper/* # 存放路径
-
在src/main/java/com/mp/mapper/UserMapper.xml创建此xml并添加如下内容:
-
执行:
三. 分页
3.1 基础分页
- 原有分页弊端: 使用PageHelper 分页,会先查询出所有数据再返回分页的数据,当数据量很大的时候,会造成查询数据很慢,对服务器性能也有一定影响;
- MP分页插件实现物理分页,操作如下:
- 添加插件:在src/main/java/com/mp/configuration/MybatisPlusConfig.java:
@Configuration
public class MyBatisPlusConfig{
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
2. 执行查询操作:
@Test
public void selectPage(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.ge("age",26);
Page<User> page=new Page<User>(1,2);
IPage<User> page= userMapper.selectPage(page,queryWrapper);
System.out.println("总页数"+iPage.getPages());
System.out.println("总记录数"+iPage.getTotal());
List<User> userList=iPage.getRecords();
userList.forEach(System.out::println);
}
- 查询语句图示效果:
上面的返回是直接是数据,还有一种方法是返回Map类型的,返回的key为字段名,value为字段值,图示如下:
@Test
public void selectPage(){
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.ge("age",26);
Page<User> page=new Page<User>(1,2);
IPag<Map<String,Object>> iPage=userMapper.selectMapsPage(page,queryWrapper);
System.out.println("总页数"+iPage.getPages());
System.out.println("总记录数"+iPage.getTotal());
)}
需要用哪种根据需求而定; 有些场景中只需要查询记录不需要查询总记录数的,我们可以设置Page参数实现只查询一条语句; (平时是两条SQL语句,一条是查询总记录数,一条是查询具体数据的); 下面图示中的Page(long current,long size,boolean isSerchCount) 这个方法中第三个参数如果为false则不查询总记录数
3.2 注解类型的自定义分页和xml形式的自定义分页
- 自定义分页操作流程:
- 在UserMapper中:
public interface UserMapper extends BaseMapper<User>{
IPage<User> selectUserPage(Page<User> page,@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}
2. XML的形式图示:
3. 测试:
里面演示的是单表的数据,如果需要多表查询的话,则sql语句进行对应的改变即可;
三. 更新update
3.1 根据Id进行更新
- 代码图示:
- 生成语句:
上面是根据ById来进行修改的,如果通过其他的条件来,我们可以使用下面的方式
- 生成语句:
如果在实体类参数和条件参数都有对一个属性进行定义,那么这两个地方的定义都生效; 在更新少量字段的时候,可以使用set 比如将年龄29改为30 ,图示如下:
3.2 Lambda方式
-
图示:
-
链式Lambda条件构造器
链式写法,返回的结果是影响记录数,如果为0则返回false;
四. 删除
4.1 内容:
- 根据id删除的方法
- 其他普通删除方法
- 以条件构造器为参数的删除方法
4.2 根据Id删除,图示:
4.3根据Map数据删除,图示:
4.4 删除一个或多个记录,
- 图示效果:
- 图示操作:
4.5 Lambda方式删除
五. AR模式、主键菜单和基本配置
5.1 AR探索
- AR模式就是建立实体类与表之间的映射关联关系:
-
实体类必须继承Model
-
Mapper必须继承BaseMapper
-
简单插入示例
-
简单查询示例:
-
查询和修改示例:
-
删除示例:
-
增加或者修改
这条语句的含义是会先去查询数据库中是否有已经存在的记录,如果有的话则进行修改update操作,如果没有记录则进行insert 增加操作;
5.2 主键策略
- 在新增数据的时候,如果该表的主键是自增策略,我们则可以在实体类中的id属性上添加@TablleId(type=IdType.AUTO) 自增,这样在添加的时候就不需要再手动给主键值了;同时MP里面内置了UUID,IdWorker 等全局主键Id生成方法,设置即可;ID_WORKER_STR则为String 类型 ,图示如下:
5.3 全局主键策略设置
如果全局策略和局部策略都设置了,那么局部策略生效,它是优先于全局策略的;设置什么样的策略更为合适则最好是依据实际情况来进行判定;
5.4 配置
-
内容:
- 基本配置
- 进阶配置
- DB策略配置
-
基本配置:
-
ConfigLocation:
-
图示:
-
实际操作:
- 首先在application.yml中配置mybatis-config.xml文件路径
- 创建mybatis-config.xml文件
-
mapperLocations
-
图示:
-
实际操作:
-
注意:
Maven 多模块项目的扫描路径需以classpath*: 开头(即加载多个jar包下的XML文件)
其他的配置可以找官网上有,这里提到的是经常用的;
5.5 进阶配置:
config-location与map-underscore-to-camel-case不能同时出现
- dbType 数据库类型配置
- 图示:
这个可以不用配置,它可以根据url获取数据库的类型
- fieldStrategy
- 图示:
这里有几种策略,比如忽略"" 空字符串,null,等;也可以对给默认null值等;需要使用的请单独详细了解;
- table-prefix 表名前缀
- 在实体类中与数据库表名对应的注解可以不用加前缀,会默认加上
六. 通用Service
6.1 内容:
- 基本方法
- 批量操作方法
- 链式调用方法
6.2 概述:
- 使用通用Service,直接就可以使用查询方法,因为它里面已经写好了很多方法;
- 接口Service通过继承Iservice获得上面的方法,impl实现类通过继承ServiceImpl<Mapper参数,要操作的实体类> 并实现接口即可进行IService的方法调用,图示如下:
它里面的一些方法和通用Mapper的方法有些事不一样的,具体请直接进入源码查看
6.3 实际测试
- 链式编程: 查询
- 链式编程: 修改
- 链式编程: 删除