Mybatis plus
可以节省很多的工作,所有的CRUD
JPA yk-mapper Mybatis plus
偷懒的
简介:
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 BaseMapper
- 强大的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速入门
使用第三方组件
1.导入第三方依赖
2.研究依赖的配置
3.代码如何编写
4.提升扩展技术的能力
具体流程:
1.建表-建立对应的springboot工程,引入对应的包和依赖。
---真实开发中的
version乐观锁
deleted逻辑删除
gmt_create\get_modief
导入依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
主要安装的mysql8:
配置时区配置:
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false$useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2BB
- 传统的:2.实现pojo-dao(连接mybatis,配置papper.xml文件)-service-controller
- 使用了mybatis之后:
- pojo
- mapper接口
- 使用
package com.ithema.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ithema.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
扫描mapper文件夹:
@MapperScan("com.ithema.reggie.mapper")
例子:
1.查询全部用户:
package com.ithema.reggie;
import com.ithema.reggie.entity.Employee;
import com.ithema.reggie.mapper.EmployeeMapper;
import com.ithema.reggie.service.EmployeeService;
import net.minidev.json.JSONUtil;
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 ReggieApplicationTests {
@Autowired
private EmployeeMapper mapper;
@Test
void contextLoads() {
//参数是一个条件构造器
//查询全部用户
List<Employee> employees = mapper.selectList(null);
employees.forEach(System.out::print);
}
}
配置日志
我们所有的sql是不可见的,我们希望它是可见的,开发时需要,上线关闭即可。
1.配置日志:
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置完毕日志之后,后面的学习就能自动的生成sql语句,你会喜欢上mybatis plus
CRUD扩展
insert
@Test
public void insert(){
User us = new User();
us.setName("郝泾钊");
us.setAge("8");
mapper.insert(user)
}
发现没有插入id会自动插入id
数据库的id为全局的唯一的默认值
主键的生成策略:
对应的数据库中的主键(uuid、自增id、雪花算法、redis、zookeper!)
更新方法
@Test
public void update(){
User us = new User();
//通过条件自动拼接动态sql
us.setId("6L")
us.setName("郝泾钊");
us.setAge("8");
//注意:updateById但是参数是对象
mapper.updateById(user)
}
所有的sql都是自动帮你配置的。
查询操作
@Test
public void selectById(){
User us = userMapper.selectById(1L);
}
//测试批量查询
@Test
public void selectByBatchId(){
List<User> us = userMapper.selectBatchIds(Arrays.asList(1,2,3));
us.forEach(System.out::println);
}
//测试条件区的查询用map操作
@Test
public void selectById(){
HashMap<String,Object> map = new Hash<>();
//自定义查询的条件
map.put("name","郝泾钊");
List<User> us = userMapper.selectByMap(map);
us.forEach(System.out::println);
}
分页查询
1.原始的limit分页
2.page
拦截器:
1.配置拦截器组件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
2.内置了一个page对象
//测试分页查询 第一页,5个数据
public void testPage(){
//参数一:当前页
//参数二:页面的大小
//使用了分页之后,分页变得简单
Page<User> page = new Page <>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
删除操作
//测试分页查询 第一页,5个数据
public void testDelete(){
User us = userMapper.deleteById(1L);
}
//测试分页查询 第一页,5个数据
public void testDelete(){
List<User> us = userMapper.deleteBatchIds(Arrays.asList(1,2,3));
}
@Test
public void selectById(){
HashMap<String,Object> map = new Hash<>();
//自定义查询的条件
map.put("name","郝泾钊");
List<User> us = userMapper.deleteByMap(map);
us.forEach(System.out::println);
}
逻辑删除
逻辑删除:在数据库中没有移除 :通过 deleted =0 =>delete=1
物理删除:在数据库中移除了
管理员可以参看被删除的记录,防止数据的丢失,类似于回收站
1.在数据库中增加一个deleted字段
2.实体类添加一个deleted属性
@Tablelogid//逻辑删除注解
3.配置bean组机
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
@Bean
public ISqlInjector sqlInjector(){
return new logicSqlInjector();
}
走的是逻辑删除,但是是更新操作
查询的时候会自动过滤逻辑删除的项目--管理员看的很清楚
自动填充
创建时间、修改时间!这些这个操作自编都是自动化实现的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create\gmt_modilfed几乎所有的表都要配置上!而且自动化!
方法一:数据库级别
1.在表中新增字段create_time\update_time
dateTime类型
自动生成
方法二:代码级别
1.删除数据库的默认值
2.实体类上增加相应的注解
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
3.处理注解,编写处理器
4.测试插入
5.测试更新
乐观锁
在面试过程中,经常被问到乐观锁------悲观锁(原子引用)
认为不会出现问题,无论干什么不去上锁,如果出现了问题,就再次更新值测试
version、new version
悲观锁,认为都会出现问题,无论干什么都会上锁
乐观锁的实现
- 取出记录
- 更新时,带上version
- 执行更新操作时,set version = newVersion where version = oldVersion()
- 如果version不对,就更新失败
乐观锁:1.先查询,获得版本号 version=1
--A
update user set name ="郝泾钊",version=version+1
where id =2 and version =1
--B 线程抢先完成 这时候 version=2,就会导致A修改失败
update user set name ="郝泾钊",version=version+1
where id =2 and version =1
Mybatis Plus 的乐观锁字段
1.增加version字段,默认值为1
2.实体类加上version字段
//添加version注解
@Version
private Integer version;
3.注册组件
要添加@Configuration注解
//注册乐观锁的插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
package com.ithema.reggie.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 配置MP的分页插件
*/
@MapperScan("com.ithema.reggie.mapper")
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
4.测试乐观锁
@Test
public void OptimisticLockerInnerInterceptor(){
//1.查询用户信息
User us = userMapper.selectById("1L");
//修改用户质量
us.setName("郝泾钊");
us.setAge("20");
//并发插队
User us = userMapper.selectById("1L");
//修改用户质量
us.setName("郝泾钊");
us.setAge("20");
//执行更新操作
userMapper.updateById(us);
//执行更新操作
//自旋锁自己就能行了
userMapper.updateById(us);
}
分布式系统唯一id
雪花算法:
snowflake算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。
这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是409.6万个ID。
在这里插入图片描述
雪花算法描述:
最高位是符号位,始终为0,不可用。
41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
10位的机器标识,10位的长度最多支持部署1024个节点。10位器标识符一般是5位IDC+5位machine编号,唯一确定一台机器。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
优点:
1)不依赖于数据库,速度快,性能高。
2)ID按照时间在单机上是递增的。
3)可以根据实际情况调整各各位段,方便灵活。
缺点:
1)在单机上是递增的,由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时也会出现不是全局递增的情况。
2)只能趋势递增。(如果绝对递增,竞对中午下单,第二天再下单即可大概判断该公司的订单量,危险!)
3)依赖机器时间,如果发生回拨会导致可能生成id重复。
算法的java实现:
public class SnowflakeIdWorker {
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;
/** 机器id所占的位数 */
private final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private final long datacenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位数 */
private final long sequenceBits = 12L;
/** 机器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 数据标识id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作机器ID(0~31) */
private long workerId;
/** 数据中心ID(0~31) */
private long datacenterId;
/** 毫秒内序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) timestamp = tilNextMillis(lastTimestamp);//阻塞到下一个毫秒,获得新的时间戳
} else {//时间戳改变,毫秒内序列重置
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 1000; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
snowflake算法时间回拨问题
分析时间回拨产生原因:
1)人为操作,在真实环境一般不会出现,基本可以排除。
2)由于有些业务的需要,机器需要同步时间服务器(在这个过程中可能会存在时间回拨)。
时间问题回拨的解决方法:
1)当回拨时间小于15ms,就等时间追上来后继续生成。
2)当时间大于15ms,通过更换workid来产生之前都没有产生过的来解决。
3)把workid的位数进行调整(15位可以达到3万多,一般够用了)
在这里插入图片描述
Snowflake算法调整下位段:
sign(1bit):固定1bit符号标识,即生成的畅途分布式唯一id为正数。
delta seconds (38 bits):当前时间,相对于时间基点"2017-12-21"的增量值,单位:毫秒,最多可支持约8.716年。
worker id (15 bits):机器id,最多可支持约3.28万个节点。
sequence (10 bits):每秒下的并发序列,10 bits,这个算法单机每秒内理论上最多可以生成1000*(2^10),也就是100W的ID,完全能满足业务的需求。
由于服务无状态化关系,所以一般workid也并不配置在具体配置文件里面,这里我们选择redis来进行中央存储(zk、db)都是一样的,只要是集中式的就可以。
现在把3万多个workid放到一个队列中(基于redis),由于需要一个集中的地方来管理workId,每当节点启动时,(先在本地某个地方看看是否有借鉴弱依赖zk本地先保存),如果有那么值就作为workid,如果不存在,就在队列中取一个当workid来使用(队列取走了就没了 ),当发现时间回拨太多的时候,我们就再去队列取一个来当新的workid使用,把刚刚那个使用回拨的情况的workid存到队列里面(队列我们每次都是从头取,从尾部进行插入,这样避免刚刚a机器使用又被b机器获取的可能性)。
主键的生成策略
默认全局唯一id @TableId(type = IdType.ID_WORKER)
主键自增 数据库也必须是自增的不然报错 @TableId(type = IdType.AUTO)
其他的源码解释: // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.baomidou.mybatisplus.annotation; public enum IdType { AUTO(0),//数据库id自增 NONE(1),//未设置主键 INPUT(2),//手动输入 ASSIGN_ID(3),//默认的全局id ASSIGN_UUID(4),//默认的唯一id uuid /** @deprecated */ @Deprecated ID_WORKER(3), /** @deprecated */ @Deprecated ID_WORKER_STR(3),//字符串表示法 /** @deprecated */ @Deprecated UUID(4); private final int key; private IdType(int key) { this.key = key; } public int getKey() { return this.key; } }
性能分析插件
开发中会遇到一些慢sql,测试
MP也提供一些性能分析的插件,好像找不到了
条件构造器
wrapper
十分重要
写一些复杂的构造
简单的非空的条件:
@Test
void contextLoads(){
//查询name不为空,年龄等于12
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",12);
usermapper.selectList(wrapper).forEach(System.out::println);
}
条件有参数查询
@Test
void test2(){
//查询name不为空,年龄等于12
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper
.eq("name","郝泾钊");
//查询一个selectOne 别的用List map
usermapper.selectOne(wrapper);
}
范围:
@Test
void test3(){
//年龄在20-30之间
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper
.between("age",20,30);
//查询一个selectOne 别的用List map
usermapper.selectCount(wrapper);//数量数目
}
模糊查询
@Test
void test4(){
//年龄在20-30之间
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper
.notLike("name","0")
.likeRight("email","t");//右边
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::print)
//查询一个selectOne 别的用List map
usermapper.selectCount(wrapper);//数量数目
}
嵌套sql
@Test
void test5(){
//年龄在20-30之间
QueryWrapper<User> wrapper=new QueryWrapper<>();
//id在子查询中查出来
wrapper.inSql("id","select id from user where id<3");
List<Object> objects = userMapper.selectPbjects(wrapper);
maps.forEach(System.out::print)
}
排序:
@Test
void test5(){
//年龄在20-30之间
QueryWrapper<User> wrapper=new QueryWrapper<>();
//id在子查询中查出来
wrapper.orderByDesc("id");
List<User> objects = userMapper.selectList(wrapper);
maps.forEach(System.out::print)
}
代码生成器
main.class:
package com;
import com.baomidou.mybatisplus.annotation.DbType;
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 Main {
public static void main(String[] args) {
//创建对象
AutoGenerator autoGenerator = new AutoGenerator();
//数据源
DataSourceConfig dataSourceConfig= new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/text");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
autoGenerator.setDataSource(dataSourceConfig);
//全局配置
GlobalConfig globalConfig=new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
globalConfig.setAuthor("郝泾钊");
globalConfig.setOpen(false);
//去掉Service
globalConfig.setServiceName("%sService");
autoGenerator.setGlobalConfig(globalConfig);
//包配置
PackageConfig packageConfig =new PackageConfig();
packageConfig.setParent("com.southwind");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setController("controller");
//策略配置
StrategyConfig strategyConfig =new StrategyConfig();
strategyConfig.setInclude("people");
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
strategyConfig.setEntityLombokModel(true);
autoGenerator.setStrategy(strategyConfig);
//启动
autoGenerator.execute();
}
}