02-MyBatis
MyBatis-Plus实现数据库crud操作
1.mp是什么
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2.创建并初始化数据库
快速开始 | MyBatis-Plus (baomidou.com)
-
创建数据库,创建数据库表
-
创建工程 springboot
可以使用 Spring Initializer (opens new window)快速初始化一个 Spring Boot 工程
-
引入依赖
<!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
配置数据库信息
spring: datasource: username: root password: 123456 url: "jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8" driver-class-name: com.mysql.cj.jdbc.Driver
-
编写实体类
User
package com.mj.demomptest.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
-
mapper接口
UserMapper
package com.mj.demomptest.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mj.demomptest.entity.User; public interface UserMapper extends BaseMapper<User> { }
-
包扫描
package com.mj.demomptest; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.mj.demomptest.mapper") /* mapper 是一个interface接口动态生成实现类对象 动态生成对象默认找不到 @MapperScan 才能找到动态生成的对象 */ public class DemomptestApplication { public static void main(String[] args) { SpringApplication.run(DemomptestApplication.class, args); } }
-
测试
package com.mj.demomptest; import com.mj.demomptest.entity.User; import com.mj.demomptest.mapper.UserMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class DemomptestApplicationTests { @Autowired private UserMapper userMapper; @Test public void findAll() { System.out.println(userMapper.selectList(null)); } }
-
查看sql输出日志
#mybatis日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
控制台输出
3.mp实现添加 修改
添加
@Test
public void testAdd(){
User user = new User();
user.setName("Lucy");
user.setAge(20);
user.setEmail("09876@qq.com");
//返回影响行数
int insert = userMapper.insert(user);
System.out.println(insert);
}
修改
//修改
@Test
public void testUpdate(){
User user = new User();
user.setId(1585808430935887874L);
user.setName("Mary");
int count = userMapper.updateById(user);
System.out.println(count);
}
4.组件策略
1、插入操作
//添加
@Test
public void testAdd() {
User user = new User();
user.setName("lucy");
user.setAge(20);
user.setEmail("1243@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}
注意:数据库插入id值默认为:全局唯一id
1585808430935887874
2、MP的主键策略
2.1 ASSIGN_ID
MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)
@TableId(type = IdType.ASSIGN_ID)``private String id;
雪花算法:分布式ID生成器
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 – 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
2.2 AUTO 自增策略
需要在创建数据表的时候设置主键自增
实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
要想影响所有实体的配置,可以设置全局主键配置
#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
5.mp自动填充 乐观锁
使用相同的方式填充
自动填充
-
准备工作
-
在表中添加两个字段
添加datatime类型的新的字段,create_time,update_time
-
在表对应实体类添加对应的属性
-
-
实体类修改
自动填充属性添加注解
@TableField(fill = FieldFill.INSERT) //添加的时候设置值 private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) //添加的时候设置值,修改的时候不设置值 private Date updateTime;
-
创建实体类实现接口,实现接口两个方法
一个方法添加执行,一个方法修改执行
设置添加什么值
注意:不要忘记添加 @Component 注解
package com.mj.demomptest.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; @Component public class MyMetaObjectHandler implements MetaObjectHandler { //mp执行添加操作时 @Override public void insertFill(MetaObject metaObject) { //属性名称,设置的值(当前时间),metaObject对象 ;当前时间 set createTime中去 this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new Date(),metaObject); } //mp执行修改时 @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime",new Date(),metaObject); } }
乐观锁
多线程操作中或并发操作中,多人更改同一条数据,最后提交的事务,把之前的事务覆盖,丢失更新问题
- 取出记录时,获取当前的版本号 version
- 更新时,带上当前version
- 执行更新时, set version = newVersion where version = oldVersion 比较当前修改数据版本和数据库版本是否一样
- 如果 version 不对 ,就更新失败
实现
-
修改实体类
在表添加字段作为版本号,在表对应实体类添加版本号属性
@Version private Integer version;
-
创建配置文件 注册乐观锁插件
创建包config,创建文件MybatisPlusConfig.java
此时可以删除主类中的 @MapperScan 扫描注解
package com.mj.demomptest.config; import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.mj.demomptest.mapper") public class MpConfig { //乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
6.查询
1,查询
-
通过多个id批量查询
//多个id批量查询 @Test public void testSelect1(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); System.out.println(users); }
-
简单的条件查询
@Test public void testSelect2(){ Map<String,Object> columnMap = new HashMap<>(); columnMap.put("name","Jone"); columnMap.put("age",20); List<User> users = userMapper.selectByMap(columnMap); System.out.println(users); }
2,分页
-
配置分页查询插件
//分页查询插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
-
编写分页代码
-
插件Page对象,传入两个参数
当前页
每页显示数
-
调用mp的方法实现分页
//分页查询 @Test public void testSelectPage(){ Page<User> page = new Page(1,3); //new 的对象,条件 Page<User> userPage = userMapper.selectPage(page, null); //返回对象得到分页所有数据 long pages = userPage.getPages();//总页数 long current = userPage.getCurrent();//当前页 List<User> records = userPage.getRecords();//查询数据集合 long total = userPage.getTotal();//总记录数 boolean hasNext = userPage.hasNext();//现在当前页是否有下一页 boolean hasPrevious = userPage.hasPrevious();//现在当前页是否有上一页 }
-
7.删除与逻辑删除
1,删除
-
根据Id删除
//id删除 @Test public void testDeleteId(){ int rows = userMapper.deleteById(1); System.out.println(rows); }
-
批量删除
@Test public void testDeleteBatchIds() { int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10)); system.out.println(result); }
-
简单条件删除
@Test public void testDeleteByMap() { HashMap<String, Object> map = new HashMap<>(); map.put("name", "Helen"); map.put("age", 18); int result = userMapper.deleteByMap(map); system.out.println(result); }
2,逻辑删除
-
物理删除和逻辑删除
物理删除:表中数据不存在了
逻辑删除:表中数据还存在,但在查询时,查不出来
在表添加字段,作为逻辑删除标志,每次删除时,修改标志位
-
逻辑删除实现流程
-
数据库修改
添加delete字段 作为逻辑删除的标志
-
实体类修改
添加deleted 字段,并加上 @TableLogic 注解
@TableLogic @TableField(fill = FieldFill.INSERT) private Integer deleted; //逻辑删除标志
MyMetaObjectHandler.java
this.setFieldValByName("deleted",0,metaObject);
-
配置(可选)
0 – – -不删除
1 – – – 删除
-
测试
注意:被删除前,数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
@Test public void testLogicDelete() { int result = userMapper.deleteById(1585882038232223746L); System.out.println(result); }
数据库中的数据并没有被删除,只是修改了 deleted的值 为1
-
测试逻辑后的删除查询
-
8.条件构造器和常用接口
1,wapper介绍
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
2,测试用例
-
ge 、gt、le、lt、isNull、isNotNull
@Test public void testSel(){ //ge,gt,le,lt QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //表中的字段名字,值 age 大于等于21的 queryWrapper.ge("age",21);//大于等于 List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
sdf
-
eq(等于)、ne(不等于)
注意:seletOne()返回的是一条实体记录,当出现多条时会报错
//eq ne @Test public void testSelectOne(){ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name","Tom"); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
-
between、notBetween
包含大小边界
//between,notbetween @Test public void testSelectA(){ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //字段,开始值,结束值 age [22,28] queryWrapper.between("age",22,28); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
-
like、notlike、likeLeft、likeRight(%的右边)
update user set name like "%张"
selectMap()返回Map集合列表,通常配合select()使用
@Test public void testSelectB(){ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // queryWrapper.like("name","J"); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
-
orderBy、orderByDesc、orderByAsc 排序
@Test public void testSelectC(){ QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // queryWrapper.orderByDesc("id"); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
3,查询方式
查询方式 | 说明 |
---|---|
setSqlSelect | 设置 SELECT 查询字段 |
where | WHERE 语句,拼接 + WHERE 条件 |
and | AND 语句,拼接 + AND 字段=值 |
andNew | AND 语句,拼接 + AND (字段=值) |
or | OR 语句,拼接 + OR 字段=值 |
orNew | OR 语句,拼接 + OR (字段=值) |
eq | 等于= |
allEq | 基于 map 内容等于= |
ne | 不等于<> |
gt | 大于> |
ge | 大于等于>= |
lt | 小于< |
le | 小于等于<= |
like | 模糊查询 LIKE |
notLike | 模糊查询 NOT LIKE |
in | IN 查询 |
notIn | NOT IN 查询 |
isNull | NULL 值查询 |
isNotNull | IS NOT NULL |
groupBy | 分组 GROUP BY |
having | HAVING 关键词 |
orderBy | 排序 ORDER BY |
orderAsc | ASC 排序 ORDER BY |
orderDesc | DESC 排序 ORDER BY |
exists | EXISTS 条件语句 |
notExists | NOT EXISTS 条件语句 |
between | BETWEEN 条件语句 |
notBetween | NOT BETWEEN 条件语句 |
addFilter | 自由拼接 SQL |
last | 拼接在最后,例如:last(“LIMIT 1”) |