MP入门
Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发、提高效率而生。
Mybatis-Plus已经封装好了大量增删改查的方法,程序员只需要继承BaseMapper就可以使用这些方法了,无需自己再开发。
案例实现
向数据库保存一个User对象
创建数据表
sql
create table tb_user (
id bigint(20) primary key auto_increment,
username varchar(30) unique not null,
name varchar(30) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null,
create_time datetime,
update_time datetime
);
insert into tb_user values(1,'zhangsan1','tom','123456',12,'12345678911',now(),now());
insert into tb_user values(2,'zhangsan2','jack','123456',8,'12345678912',now(),now());
insert into tb_user values(3,'zhangsan3','jerry','123456',15,'12345678910',now(),now());
insert into tb_user values(4,'zhangsan4','tom','123456',9,'12345678910',now(),now());
insert into tb_user values(5,'zhangsan5','snake','123456',28,'12345678910',now(),now());
insert into tb_user values(6,'zhangsan6','张益达','123456',22,'12345678910',now(),now());
insert into tb_user values(7,'zhangsan7','张大炮','123456',16,'12345678910',now(),now());
创建工程
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!--mybatis plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--lombok简化对象书写-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<!--整合测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--web环境依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
创建实体类
java
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String name;
private String password;
private Integer age;
private String tel;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
创建接口
自定义的Mapper接口需要实现BaseMapper<实体类>,然后就可以继承到BaseMapper中定义的方法了
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
//声明方法+注解实现sql
}
添加配置文件
在resources中添加配置文件application.yml,然后在里面加入下面配置
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp
username: root
password: 123456
# druid 阿里 HkariCp(springBoot)
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志打印
map-underscore-to-camel-case: true # 驼峰映射
创建启动类
java
@SpringBootApplication
public class MpApplication {
public static void main(String[] args) {
SpringApplication.run(MpApplication.class,args);
}
}
测试
java
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
//1. 封装user对象
User user = User.builder()
.username("lisi1")
.name("tom")
.password("123456")
.age(18)
.tel("13700137001")
.build();
//2. 调用mapper方法
userMapper.insert(user);
}
}
案例细节
@TableName
标注在实体类上,用于声明实体类对应的表,如果表名和类名一致可以省略
java
//标注在实体类上,用于声明实体类对应的表,如果表名和类名一致可以省略
@TableName("tb_user")
public class User {
....
}
如果大部分表都是以固定前缀开头,可以全局配置表前缀,但优先级比注解配置低
yaml
mybatis-plus:
global-config:
db-config:
table-prefix: tb_ #表前缀
@TableId
标注在主键字段上,用于声明主键生成策略
java
//主键生成策略
//AUTO : 数据库的自增
//INPUT: 让用户自己输入主键
//ASSIGN_ID: 雪花算法生成的一个纯数字
//ASSIGN_UUID: UUID生成一个不重复字符串
//ASSIGN_NONE: INPUT+ASSIGN_ID
@TableId(type = IdType.AUTO)
private Long id;
值 | 描述 |
---|---|
AUTO | 数据库主键自增 |
INPUT | 手动设置主键值 |
ASSIGN_ID | 由雪花算法生成的纯数字 |
ASSIGN_UUID | UUID生成的字符串 |
NONE | 默认值 相当于INPUT+ASSIGN_ID |
如果大部分表主键都是自增,可以进行全局设置,但优先级比注解配置低
yaml
mybatis-plus:
global-config:
db-config:
id-type: auto #主键策略
table-prefix: tbl_ #表前缀
Mapper接口
BaseMapper:通用 CRUD 接口,内部声明了大量的单表操作方法,泛型
T
为任意实体对象
java
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
基本使用
MybatisPlus提供了单表的增删改查方法,常用的如下
java// 插入一条记录 int insert(T entity); // 主键查询 T selectById(Serializable id); // 主键批量查询 List<T> selectBatchIds(Collection idList); // 根据ID修改不为空的字段 int updateById(T entity); // 根据ID删除 int deleteById(Serializable id); // 根据ID集合批量删除 int deleteBatchIds(Collection idList);
java
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
//插入一条记录
//int insert(T entity);
@Test
public void testInsert() {
//1. 封装user对象
User user = User.builder()
.username("lisi1")
.name("tom")
.password("123456")
.age(18)
.tel("13700137001")
.build();
//2. 调用mapper方法
userMapper.insert(user);
}
//主键查询
//T selectById(Serializable id);
@Test
public void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
//主键批量查询
//List<T> selectBatchIds(Collection idList);
@Test
public void testSelectBatchIds() {
//1. 构建id集合
List<Long> idList = ListUtil.of(1L, 2L, 3L);
//2. 执行查询
List<User> userList = userMapper.selectBatchIds(idList);
System.out.println(userList);
}
//根据ID修改不为空的字段
//int updateById(T entity);
@Test
public void testUpdateById() {
//1. 封装user对象
User user = User.builder()
.username("lisi2")
.name("tom2")
.tel("13700137002")
.id(2L)//不要忘记设置id
.build();
userMapper.updateById(user);
}
//根据ID删除
//int deleteById(Serializable id);
@Test
public void testDeleteById() {
userMapper.deleteById(7L);
}
//根据ID集合批量删除
//int deleteBatchIds(Collection idList);
@Test
public void testDeleteBatchIds() {
List<Long> idList = ListUtil.of(1L, 2L, 3L);
userMapper.deleteBatchIds(idList);
}
}
条件查询
MybatisPlus提供了Wrapper对象来封装各种条件,比如条件、分页、排序、分组、过滤等等
java// 条件查询,当查询结果最多为一条记录时使用 手机号 身份证号 用户名 唯一约束 T selectOne(Wrapper<T> queryWrapper); // 条件查询,当查询结果可能为多条记录时使用 List<T> selectList(Wrapper<T> queryWrapper);
书写格式
MybatisPlus支持使用多种格式组装条件,我们推荐使用Lambda格式
java
@SpringBootTest
public class UserMapperTest2 {
@Autowired
private UserMapper userMapper;
//根据name=李四和age>18查询(支持动态sql)
@Test
public void testSelectList1() {
//0. 模仿前端传入参数
String name = null;
Integer age = 18;
//1. 构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
if (StrUtil.isNotEmpty(name)) {
wrapper.eq("name", name); //where name = '李四'
}
if (age != null) {
wrapper.gt("age", age);//and age > 18
}
//2. 查询
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
//Lambda 根据name=李四和age>18查询(支持动态sql)
@Test
public void testSelectList2() {
//0. 模仿前端传入参数
String name = "李四";
Integer age = 18;
//1. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//参数1位置是一个boolean值,只有这个值为true,当前条件才会生效
wrapper.eq(StrUtil.isNotEmpty(name), User::getName, name);//where name = '李四'
wrapper.gt(age != null, User::getAge, age);//and age > 18
//2. 查询
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
//Lambda链式 根据name=李四和age>18查询(支持动态sql)
@Test
public void testSelectList3() {
//0. 模仿前端传入参数
String name = "李四";
Integer age = 18;
//1. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//参数1位置是一个boolean值,只有这个值为true,当前条件才会生效
wrapper.eq(StrUtil.isNotEmpty(name), User::getName, name)//where name = '李四'
.gt(age != null, User::getAge, age);//and age > 18
//2. 查询
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
}
查询条件
查询方法 | 说明 | 例子 |
---|---|---|
eq、ne、gt、ge、lt、le、isNull、isNotNull | 比较运算 | eq("name", "老王")---> name = '老王' |
like、notLike、likeLeft、likeRight | 模糊查询 | likeRight("name", "王")---> name like '王%' |
in、notIn、between、notBetween | 范围运算 | in("age",{1,2,3})---> age in (1,2,3) |
or、and | 拼接 | eq("id",1).or().eq("name","老王")---> id = 1 or name = '老王' |
java
//select * from tb_user where
//id >= 1
//and username = 'baima'
//and name like '%四'
//and age between 10 and 30
//or tel in ('1370013001','1370013002')
@Test
public void testSelectList4() {
//0. 模仿前端传入参数
String name = "李四";
Integer age = 18;
//1. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getId,1)
.eq(User::getUsername,"lisi")
.likeLeft(User::getName,'四')
.between(User::getAge,10,30)
.or()
.in(User::getTel,List.of("1370013001","1370013002"));
//2. 查询
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
其他条件
除了设置查询条件外,MP还支持:投影、排序、分组、过滤等功能
查询方法 | 说明 | 例子 |
---|---|---|
select | 投影 | select("name","password")---> select name,password from 表 |
orderByAsc、orderByDesc | 排序 | orderByDesc("id", "name")---> order by id DESC,name DESC |
groupBy | 分组 | groupBy("id", "name")---> group by id,name |
having | 过滤 | having("sum(age) > 10")---> having sum(age) > 10 |
java
@SpringBootTest
public class UserMapperTest2 {
@Autowired
private UserMapper userMapper;
//投影和排序
//select name,age from tb_user where id > 1 order by age desc
@Test
public void testSelectList1() {
//1. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getId,1);//id > 1
wrapper.select(User::getName,User::getAge);//select name,age
wrapper.orderByDesc(User::getAge);//order by age desc
wrapper.orderByAsc(User::getId);//order by id asc
//2. 查询
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
//分组和过滤
//select age,count(*) from tb_user group by age having count(*) >= 2
@Test
public void testSelectList2() {
//1. 构建查询条件(LambdaQueryWrapper不支持分组和过滤)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("age","count(*)");//select age,count(*)
wrapper.groupBy("age");//group by age
wrapper.having("count(*) >= 2");//having count(*) >= 2
//2. 查询
List<Map<String, Object>> list = userMapper.selectMaps(wrapper);
list.forEach(System.out::println);
}
//分组和过滤
//select age,count(*) from tb_user group by age having count(*) >= 2
@Test
public void testSelectList3() {
List<Map<String, Object>> list = userMapper.count1();
list.forEach(System.out::println);
}
@Test
public void testSelectList4() {
List<Re> list = userMapper.count2();
list.forEach(System.out::println);
}
}
UserMapper
java
//自定义的Mapper 要求继承BaseMapper<实体类类型>
@Mapper
public interface UserMapper extends BaseMapper<User> {
//自定义
@Select("select age,count(*) from tb_user group by age having count(*) >= 2")
List<Map<String, Object>> count1();
@Select("select age,count(*) as num from tb_user group by age having num >= 2")
List<Re> count2();
}
Re
java
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Re {
private String age;
private Integer num;
}
分页查询
MybatisPlus内置了专门用于分页的插件,使用起来非常简单,它是基于拦截器原理实现分页的
① 配置拦截器
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
② 分页代码实现
java
//分页查询
//select * from tb_user where id > 1 limit 5,7
@Test
public void testPage() {
//1. 设置分页条件 当前页面 每页条数
Page<User> page = new Page<>(2, 3);
//2. 设置业务条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getAge, 18);
//3. 调用分页方法
page = userMapper.selectPage(page, wrapper);
//4. 获取分页结果
System.out.println("总条数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("当前页数据集合:" + page.getRecords());
}
条件修改
java// 参数1: 封装要修改的字段 参数2: 封装更新条件 int update(T entity,Wrapper<T> updateWrapper);
java
@SpringBootTest
public class UserMapperTest4 {
@Autowired
private UserMapper userMapper;
//条件更新
//update tb_user set age = 10,password = '123123' where name = 'tom'
@Test
public void testUpdate() {
//1. 设置更新条件
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "tom");
//2. 设置更新字段
User user = User.builder()
.age(10)
.password("123123")
.build();
//3. 执行更新
userMapper.update(user, wrapper);
}
}
条件删除
java// 条件删除 int delete(Wrapper<T> wrapper);
java
//条件删除
//delete from tb_user where name = 'tom';
@Test
public void testDelete() {
//1. 设置删除条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "tom");
//2. 执行删除
userMapper.delete(wrapper);
}
实用功能
逻辑删除
逻辑删除指的是:当执行删除操作时,并不是将对应记录真正从数据库删掉,而是使用一个标识列,将要删除的数据标识为删除状态
MP使用@TableLogic就可以轻松实现这个功能
①:在user表中添加逻辑删除标记字段,并设置默认值为0
②:实体类中添加对应字段,设定为当前字段为逻辑删除标记字段
java
//逻辑删除字段:value用于指定未删除状态的值, delval用于指定删除状态的值
@TableLogic(value = "0", delval = "1")
private Integer deleted;
③ 删除其中一个用户观察效果
@TableLogic 只是单个表设置逻辑删除字段,如果多张表都需要配置逻辑删除,则可以做全局配置
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
自动填充
对于数据表中的一些公共字段,我们可以使用mybatisplus的自动填充功能来统一设置值
@TableField注解的fill属性可以完成这个功能 [1. 什么时候填充 2. 填充什么值]
① 在实体类的字段上添加注解
java
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
值 | 描述 |
---|---|
INSERT | 插入时填充字段 |
UPDATE | 更新时填充字段 |
INSERT_UPDATE | 插入和更新时填充字段 |
② 在配置类中设置要填充的值
java
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//新增时执行此方法
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
//修改时执行此方法
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
③ 测试
java
//自动填充
@Test
public void testFill(){
//1. 封装user对象
User user = User.builder()
.username("lisi10")
.name("tom10")
.password("123456")
.age(18)
.tel("13700137001")
.build();
//2. 调用mapper方法
userMapper.insert(user);
}
多余属性
多余属性指的是:实体类中存在,但是在数据表没有对应字段的属性
此时需要使用@TableField(exist = false)标识此属性
java
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
//标注在实体类上,用于声明实体类对应的表,如果表名和类名一致可以省略
//@TableName("tb_user")
public class User {
//type: 声明主键生成策略
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String name;
private String password;
@TableField(exist = false)//表示当前属性仅在实体类中存在,在数据表中没有对应的字段
private String password2;
private Integer age;
private String tel;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//逻辑删除字段:value用于指定未删除状态的值, delval用于指定删除状态的值
//@TableLogic(value = "1", delval = "0")
private Integer deleted;
}
ID精度损失问题
后端返回一个Long的id时候,前端接收到的数据精度有损失
pom.xml
xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
WebMvcConfig
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//扩展mvc框架的消息转换器
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0, messageConverter);
}
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public JacksonObjectMapper() {
super();
//对应JDK8+ 时间类型处理需要添加的模块
this.registerModule(new JavaTimeModule());
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(FAIL_ON_UNKNOWN_PROPERTIES);
//自定义转换规则
SimpleModule simpleModule = new SimpleModule()
.addSerializer(BigInteger.class, ToStringSerializer.instance)//将BigInteger转换为String
.addSerializer(Long.class, ToStringSerializer.instance);//将Long转换成String
this.registerModule(simpleModule);
}
}
}
Service接口
介绍
为了简化service代码编写,mybatisPlus也提供了的基础接口和实现类
我们只需要让我们自己的service去继承它提供的service,就可以使用里面的方法
进一步封装 CRUD 采用 get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆
分类 | 方法 | 描述 |
---|---|---|
新增 | boolean save(T entity) | 新增,entity 实体对象 |
新增 | boolean saveOrUpdate(T entity) | id存在则更新记录,否插入一条记录 |
新增 | boolean saveBatch(Collection entityList) | 插入(批量),默认一次可以保存1000条数据 |
修改 | boolean updateById(T entity) | 根据 ID 修改 |
修改 | boolean update(T entity,Wrapper updateWrapper) | 根据 条件 修改 |
查询 | T getById(Serializable id) | 根据 ID 查询 |
查询 | List listByIds(Collection idList) | 查询(根据ID 批量查询) |
查询 | List list() | 查询所有 |
查询 | List list(Wrapper queryWrapper) | 条件查询 |
删除 | boolean removeById(Serializable id) | 根据 ID 删除 |
删除 | boolean removeByIds(Collection idList) | 删除(根据ID 批量删除) |
删除 | boolean remove(Wrapper queryWrapper) | 根据条件删除 |
修改Service
使用Service 接口使用
- 接口继承 IService
- 实现类继承 ServiceImpl<M,T>
UserService
java
//自定义service接口
public interface UserService extends IService<User> {
}
UserServiceImpl
java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
修改controller
java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
public Result findList(){
List<User> userList = userService.list();
return Result.success(userList);
}
@PostMapping("/user")
public Result save(@RequestBody User user){
userService.saveOrUpdate(user);
return Result.success();
}
@DeleteMapping("/user/{id}")
public Result deleteById(@PathVariable("id") Long id){
userService.removeById(id);
return Result.success();
}
@GetMapping("/user/{id}")
public Result findById(@PathVariable("id") Long id){
User user = userService.getById(id);
return Result.success(user);
}
@PutMapping("/user")
public Result update(@RequestBody User user){
userService.saveOrUpdate(user);
return Result.success();
}
}