目录
MybatisPlus简介
入门案例
MybatisPlus概述
标准数据层开发
标准数据层CRUD功能
分页功能
DQL控制
条件查询方式
查询投影
查询条件设定
字段映射与表名映射
DML控制
Insert
Delete
乐观锁
快速开发
代码生成器原理分析
MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。
开发方式
基于MyBatis使用MyBatisPlus
基于Spring使用MyBatisPlus
基于SpringBoot使用MyBatisPlus
步骤1:创建数据库及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user
(
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null,
tel varchar(32) not null
);
insert into user
values (1, 'Tom', 'tom', 3, '18866668888');
insert into user
values (2, 'Jerry', 'jerry', 4, '16688886666');
insert into user
values (3, 'Jock', '123456', 41, '18812345678');
步骤2:创建SpringBoot工程
步骤3:勾选配置使用技术
说明:
由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加
步骤4:pom.xml补全依赖
com.baomidou
mybatis-plus-boot-starter
3.4.1
com.alibaba
druid
1.1.16
说明:
druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源
从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入,
不需要额外在添加MyBatis的相关jar包
步骤5:添加MP的相关配置信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
步骤6:根据数据库表创建实体类
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//setter...getter...toString方法略
}
步骤7:创建Dao接口
@Mapper
public interface UserDao extends BaseMapper {
}
步骤8:编写引导类
@SpringBootApplication
//@MapperScan("com.green.dao")
public class Mybatisplus01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
}
}
说明:Dao接口要想被容器扫描到,有两种解决方案:
方案一:在Dao接口上添加 @Mapper 注解,并且确保Dao处在引导类所在包或其子包中
该方案的缺点是需要在每一Dao接口中添加注解
方案二:在引导类上添加 @MapperScan 注解,其属性为所要扫描的Dao所在包
该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到, @Mapper 就可以不写。
步骤9:编写测试类
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List userList = userDao.selectList(null);
System.out.println(userList);
}
}
说明:
userDao注入的时候下面有红线提示的原因是什么?
UserDao是一个接口,不能实例化对象
只有在服务器启动IOC容器初始化后,由框架创建DAO接口的代理对象来注入
现在服务器并未启动,所以代理对象也未创建,IDEA查找不到对应的对象注入,
所以提示报红一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
MyBatisPlus的官网为: https://mp.baomidou.com/
说明:
MP的特性:
无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
支持 Lambda:编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件
..
新增
int insert (T t)
T:泛型,新增用来保存新增数据
int:返回值,新增成功后返回1,没有新增成功返回的是0
删除
int deleteById (Serializable id)
Serializable:参数类型
int:返回值类型,数据删除成功返回1,未删除数据返回0。
思考:参数类型为什么是一个序列化类?
从这张图可以看出,
String和Number是Serializable的子类,
Number又是Float,Double,Integer等类的父类,
能作为主键的数据类型都已经是Serializable的子类,
MP使用Serializable作为参数类型,就好比可以用Object接收任何数据类型一样。
修改
int updateById(T t);
T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值
int:返回值,修改成功后返回1,未修改数据返回0
根据ID查询
T selectById (Serializable id)
Serializable:参数类型,主键ID的值
T:根据ID查询只会返回一条数据
查询所有
List selectList(Wrapper queryWrapper)
Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
List:因为查询的是所有,所以返回的数据是一个集合
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//添加
@Test
void testAdd(){
User user = new User();
user.setName("张三");
user.setPassword("123");
user.setAge(23);
user.setTel("1233456677");
userDao.insert(user);
}
//删除
@Test
void testDelete(){
userDao.deleteById(1658485544068743170L);
}
//修改
@Test
void testUpdate(){
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("Tom888");
//提供什么字段修改什么字段
userDao.updateById(user);
}
//查询单个数据
@Test
void testGetById(){
User user = userDao.selectById(2L);
System.out.println(user);
}
//查询所有
@Test
void testGetAll() {
List userList = userDao.selectList(null);
System.out.println(userList);
}
}
Lombok
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
使用步骤
步骤1:添加lombok依赖
org.projectlombok
lombok
注意:版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
步骤2:安装Lombok的插件
如果在IDEA中找不到lombok插件,可以访问如下网站
https://plugins.jetbrains.com/plugin/6317-lombok/versions
根据自己IDEA的版本下载对应的lombok插件,下载成功后,在IDEA中采用离线安装的方式进行安装。
步骤3:模型类上添加注解
Lombok常见的注解有:
@Setter:为模型类的属性提供setter方法
@Getter:为模型类的属性提供getter方法
@ToString:为模型类的属性提供toString方法
@EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
@Data:是个组合注解,包含上面的注解的功能
@NoArgsConstructor:提供一个无参构造函数
@AllArgsConstructor:提供一个包含所有参数的构造函数
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
IPage selectPage(IPage page, Wrapper queryWrapper)
IPage:用来构建分页查询条件
Wrapper:用来构建条件查询的条件,目前没有可直接传为Null
IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
IPage是一个接口,需要找到它的实现类来构建它,具体的实现类,
可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page 。
步骤1:调用方法传入参数获取返回值
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//分页查询
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
IPage page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());
}
}
步骤2:设置分页拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
说明:上面的代码记不住咋办呢?
这些内容在MP的官方文档中有详细的说明,可以查看官方文档类配置
步骤3:运行测试程序
如果想查看MP执行的SQL语句,可以修改application.yml配置文件
#开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
打开日志后,就可以在控制台打印出对应的SQL语句,
开启日志功能性能就会受到影响,调试完后记得关闭。
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
一个 Wrapper 类,这个类就是用来构建查询条件的,如下图所示:
环境构建
创建一个SpringBoot项目
pom.xml中添加对应的依赖
编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper { }
编写模型类
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
编写引导类
@SpringBootApplication
public class Mybatisplus02DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus02DqlApplication.class, args);
}
}
编写配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
main:
banner-mode: off
#开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
编写测试类
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
List userList = userDao.selectList(null);
System.out.println(userList);
}
}
测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以把这个日志处理下:
取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
取消MybatisPlus启动banner图标
application.yml添加如下内容:
# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
取消SpringBoot的log打印
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
构建条件查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
//查询所有
@Test
void testGetAll() {
// //方式一:按条件查询
// QueryWrapper qw = new QueryWrapper();
// qw.lt("age",18);
// List userList = userDao.selectList(qw);
// System.out.println(userList);
// //方式二:Lambda格式按条件查询
// QueryWrapper qw = new QueryWrapper();
// qw.lambda().lt(User::getAge,18);
// List userList = userDao.selectList(qw);
// System.out.println(userList);
//方式三:Lambda格式按条件查询
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
// lqw.lt(User::getAge, 30);
// lqw.gt(User::getAge, 10);
//链式编程
//lqw.lt(User::getAge, 30).gt(User::getAge, 10);//10-30之间
//小于10或者大于30
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
null判定
在做条件查询的时候,一般会有很多条件可以供用户进行选择查询。
这些条件用户可以选择使用也可以选择不使用,比如要查询价格在8000以上的手机
在输入条件的时候,价格有一个区间范围,按照需求只需要在第一个价格输入框中输入8000
后台在做价格查询的时候,一般会让 price>值1 and price <值2
因为前端没有输入值2,所以如果不处理的话,就会出现 price>8000 and price < null问题
这个时候查询的结果就会出问题,具体该如何解决?
需求:
查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
思考第一个问题:后台如果想接收前端的两个数据,该如何接收?
可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个age属性,如:
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
使用一个age属性,如何去接收页面上的两个值呢?
方案一:添加属性age2,这种做法可以但是会影响到原模型类的属性内容
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer age2;
}
方案二:新建一个模型类,让其继承User类,并在其中添加age2属性,
UserQuery在拥有User属性后同时添加了age2属性。
@Data
public class UserQuery extends User {
private Integer age2;
}
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
//查询所有
@Test
void testGetAll() {
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
// uq.setAge2(30);
//null判定
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
lqw.lt(null != uq.getAge2(), User::getAge, uq.getAge2());
lqw.gt(null != uq.getAge(), User::getAge, uq.getAge());
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
lt()方法
condition为boolean类型,返回true,则添加条件,返回false则不添加条件
查询指定字段
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.select(User::getId,User::getName,User::getAge);
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
select(...)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT id,name,age FROM user
如果使用的不是lambda,就需要手动指定字段
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper lqw = new QueryWrapper();
lqw.select("id","name","age","tel");
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
最终的sql语句为:SELECT id,name,age,tel FROM user
聚合查询
需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper lqw = new QueryWrapper();
//lqw.select("count(*) as count");
//SELECT count(*) as count FROM user
//lqw.select("max(age) as maxAge");
//SELECT max(age) as maxAge FROM user
//lqw.select("min(age) as minAge");
//SELECT min(age) as minAge FROM user
//lqw.select("sum(age) as sumAge");
//SELECT sum(age) as sumAge FROM user
lqw.select("avg(age) as avgAge");
//SELECT avg(age) as avgAge FROM user
List
分组查询
需求:分组查询,完成 group by的查询使用
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper lqw = new QueryWrapper();
lqw.select("count(*) as count,tel"); lqw.groupBy("tel");
List
注意:
聚合与分组查询,无法使用lambda表达式来完成
MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现
查询条件
MP的查询条件有很多:
范围匹配(> 、 = 、between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
..
等值查询
需求:根据用户名和密码查询用户信息
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
}
}
eq(): 相当于 = ,对应的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
范围查询
需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.between(User::getAge, 10, 30);
//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
gt():大于(>)
ge():大于等于(>=)
lt():小于(<)
lte():小于等于(<=)
between():between ? and ?
模糊查询
需求:查询表中name属性的值以J 开头的用户信息,使用like进行模糊查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.likeLeft(User::getName, "J");
//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
List userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
like():前后加百分号,如 %J%
likeLeft():前面加百分号,如 %J
likeRight():后面加百分号,如 J%
排序查询
需求:查询所有数据,然后按照id降序
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper lwq = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
* 当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getId);
userDao.selectList(lwq)
}
}
orderBy排序
condition:条件,true则添加排序,false则不添加排序
isAsc:是否为升序,true升序,false降序
columns:排序字段,可以有多个
orderByAsc/Desc(单个column):按照指定字段进行升序/降序
orderByAsc/Desc(多个column):按照多个字段进行升序/降序
orderByAsc/Desc
condition:条件,true添加排序,false不添加排序
多个columns:按照多个字段进行排序
除了上面的这几种查询条件构建方法以外还会有很多其他的方法,
比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档的条件构造器来学习使用
https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
问题1:表字段与编码属性设计不同步
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,
这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP提供了一个注解@TableField ,使用该注解可以实现模型类属性名和表的列名之间的映射关系
问题2:编码中添加了数据库中未定义的属性
当模型类中多了一个数据库表不存在的字段,
就会导致生成的sql语句中在select的时候查询了数据库不存在的字段
问题3:采用默认查询开放了更多的字段查看权限
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,
这个时候就需要限制哪些字段默认不要进行查询
@TableField 注解的一个属性叫select ,该属性设置默认是否需要查询该字段的值,
true(默认值)表示默认查询该字段,false表示默认不查询该字段
名称 |
@TableField |
类型 |
属性注解 |
位置 |
模型类属性定义上方 |
作用 |
设置当前属性对应的数据库表中的字段关系 |
相关属性 |
value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并 select:设置属性是否参与查询,此属性与select()映射配置不冲突 |
问题4:表名与编码开发设计不同步
该问题主要是表的名称和模型类的名称不一致,导致查询失败
解决方案是使用MP提供的另外一个注解 @TableName 来设置表与模型类之间的对应关系。
名称 |
@TableName |
类型 |
类注解 |
位置 |
模型类定义上方 |
作用 |
设置当前类对应于数据库表关系 |
相关属性 |
value(默认):设置数据库表名称 |
id生成策略控制
不同的表应用不同的id生成策略
日志:自增(1,2,3,4,..)
购物订单:特殊规则(FQ23948AK3843)
外卖单:关联地区日期等信息(10 04 20200314 34 91)
关系表:可省略id
...
名称 |
@TableId |
类型 |
属性注解 |
位置 |
模型类中用于表示主键的属性定义上方 |
作用 |
设置当前类中主键属性的生成策略 |
相关属性 |
value(默认):设置数据库表主键名称 type:设置主键属性的生成策略,值查照IdType的枚举值 |
环境构建
创建一个SpringBoot项目
pom.xml中添加对应的依赖
编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper { }
编写模型类
@Data
@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;
}
编写引导类
@SpringBootApplication
public class Mybatisplus03DmlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus03DmlApplication.class, args);
}
}
编写配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
main:
banner-mode: off
#开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
编写测试类
@SpringBootTest
class Mybatisplus03DmlApplicationTests {
@Autowired
private UserDao userDao;
//添加
@Test
void testAdd() {
User user = new User();
user.setName("张三");
user.setPassword("123");
user.setAge(23);
user.setTel("1233456677");
userDao.insert(user);
}
//删除
@Test
void testDelete() {
userDao.deleteById(1658485544068743170L);
}
//修改
@Test
void testUpdate() {
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("Tom888");
//提供什么字段修改什么字段
userDao.updateById(user);
}
}
代码演示
步骤1:设置生成策略为AUTO
@Data
//@TableName("tbl_user")
public class User {
//修改id生成策略
@TableId(type = IdType.AUTO)
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;
}
AUTO 的作用是使用数据库ID自增,在使用该策略的时候一定要确保对应的数据库表设置了ID主键自增,
否则无效。
从源码中可以看到,除了AUTO这个策略以外,还有如下几种生成策略:
NONE: 不设置id生成策略
INPUT:用户手工输入id
ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型)
ASSIGN_UUID:以UUID生成算法作为id生成策略
其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
拓展
分布式ID是什么?
当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
比如订单表就有可能被存储在不同的服务器上
如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
雪花算法
雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。
其生成的结果是一个
64bit大小整数,它的结构如下图:
1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。
生成的id一般都是用整数,所以最高位固定为0。
2. 41bit-时间戳,用来记录时间戳,毫秒级
3. 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,
低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
4. 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID
简化配置
模型类主键策略设置
项目中的每一个模型类上都需要使用相同的生成策略,稍微有点繁琐,在配置文件中进行配置
就能让所有的模型类都可以使用该主键ID策略
mybatis-plus:
global-config:
db-config:
id-type: auto #全局id生成策略
配置完成后,每个模型类的主键ID策略都将成为auto
数据库表与模型类的映射关系
MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以 tbl_ 开头
就需要将所有的模型类上添加 @TableName
配置起来还是比较繁琐,简化方式为在配置文件中配置如下内容:
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
多记录操作
之前添加了很多商品到购物车,过了几天发现这些东西又不想要了,该怎么办呢?
很简单删除掉,但是一个个删除的话还是比较慢和费事的,所以一般会给用户一个批量操作,
也就是前面有一个复选框,用户一次可以勾选多个也可以进行全选,
然后删一次就可以将购物车清空,这个就需要用到 批量删除 的操作。
int deleteBatchIds(@Param(Constants.COLLECTION)
Collection extends Serializable> idList);
翻译方法的字面意思为:删除(根据ID 批量删除),参数是一个集合,可以存放多个id值。
需求:根据传入的id集合将数据库表中的数据删除掉。
@SpringBootTest
class Mybatisplus03DmlApplicationTests {
@Autowired
private UserDao userDao;
//删除
@Test
void testDelete() {
List list = new ArrayList<>();
list.add(5L);
list.add(6L);
list.add(7L);
//删除多条数据
userDao.deleteBatchIds(list);
}
}
List selectBatchIds(@Param(Constants.COLLECTION)
Collection extends Serializable> idList);
方法名称翻译为:查询(根据ID 批量查询),参数是一个集合,可以存放多个id值。
需求:根据传入的ID集合查询用户信息
//查询指定多条数据
List list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
查询结果就会按照指定传入的id值进行查询
逻辑删除
这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同)的表
员工ID为1的张业绩,总共签了三个合同,如果此时他离职了,
需要将员工表中的数据进行删除,会执行delete操作
如果表在设计的时候有主外键关系,那么同时也得将合同表中的前三条数据也删除掉
后期要统计所签合同的总金额,就会发现对不上,原因是已经将员工1签的合同信息删除掉了
如果只删除员工不删除合同表数据,那么合同的员工编号对应的员工信息不存在,
那么就会出现垃圾数据,就会出现无主合同,根本不知道有张业绩这个人的存在
所以经过分析,不应该将表中的数据删除掉,而是需要进行保留,
但是又得把离职的人和在职的人进行区分,这样就解决了上述问题,如:
区分的方式,就是在员工表中添加一列数据deleted ,
如果为0说明在职员工,如果离职则将其改完1,(0和1所代表的含义是可以自定义的)
物理删除:业务数据从数据库中丢弃,执行的是delete操作
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,
数据保留在数据库中,执行的是update操作
步骤1:修改数据库表添加deleted 列
步骤2:实体类添加属性
@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
//修改id生成策略 已写全局配置
// @TableId(type = IdType.AUTO)
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;
//逻辑删除字段
@TableLogic(value = "0",delval = "1")
private Integer deleted;
}
步骤3:运行删除方法
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1L);
}
}
从测试结果来看,逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。
思考
逻辑删除,对查询有没有影响呢?
执行查询操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testFind(){
System.out.println(userDao.selectList(null));
}
}
如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加 @TableLogic 注解,如何优化?
在配置文件中添加全局配置,如下:
mybatis-plus: global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
执行的SQL语句为:
UPDATE tbl_user SET deleted=1 where id = ? AND deleted=
名称 |
@TableLogic |
类型 |
属性注解 |
位置 |
模型类中用于表示删除字段的属性定义上方 |
作用 |
标识该字段为进行逻辑删除的字段 |
相关属性 |
value:逻辑未删除值 |
业务并发现象带来的问题:秒杀
假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,
如何保证不会出现超买或者重复卖
实现思路
数据库表中添加version列,比如默认值给1
第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
第一个线程执行更新时,set version = newVersion where version = oldVersion
newVersion = version+1 [2]
oldVersion = version [1]
第二个线程执行更新时,set version = newVersion where version = oldVersion
newVersion = version+1 [2]
oldVersion = version [1]
假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
假如第一个线程先执行更新,会把version改为2,
第二个线程再更新的时候,set version = 2 where version = 1,
此时数据库表的数据version已经为2,所以第二个线程会修改失败
假如第二个线程先执行更新,会把version改为2,
第一个线程再更新的时候,set version = 2 where version = 1,
此时数据库表的数据version已经为2,所以第一个线程会修改失败
不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
实现步骤
步骤1:数据库表添加列
列名可以任意,比如使用version ,给列设置默认值为1
步骤2:在模型类中添加对应的属性
@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
//修改id生成策略 已写全局配置
// @TableId(type = IdType.AUTO)
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;
//逻辑删除字段
// @TableLogic(value = "0",delval = "1")
private Integer deleted;
//乐观锁
@Version
private Integer version;
}
步骤3:添加乐观锁的拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
//1、定义mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2、添加具体拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//3、添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
步骤4:执行更新操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Test
void testUpdate() {
// User user = new User();
// user.setId(3L);
// user.setName("Jock666");
// user.setVersion(1);
// userDao.updateById(user);
//
// //1.先通过要修改的id 将当前数据查询出来
// User user = userDao.selectById(3L);
// //2.将要修改的数据逐一设置
// user.setName("Jock888");
// userDao.updateById(user);
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock bbb");
userDao.updateById(user2); //version=4
user.setName("Jock aaa");
userDao.updateById(user); //version=3?还成立吗
}
}
乐观锁就已经实现完成了,如果对于上面的这些步骤记不住咋办呢?
参考官方文档来实现:
https://mp.baomidou.com/guide/interceptor-optimistic- locker.html#optimisticlockerinnerinterceptor
观察之前写的代码,会发现其中也会有很多重复内容,比如:
想做一个Book模块的开发,是不是只需要将红色部分的内容全部更换成 Book 即可,如:
做任何模块的开发,对于这段代码,基本上都是对红色部分的调整,
所以把去掉红色内容的东西称之为模板,红色部分称之为参数,以后只需要传入不同的参数,
就可以根据模板创建出不同模块的dao代码。
除了Dao可以抽取模块,其实常见的类都可以进行抽取,只要有公共部分即可。再来看下模型类的模板:
模板: MyBatisPlus提供,可以自己提供,但是麻烦,不建议
数据库相关配置:读取数据库获取表和字段信息
开发者自定义配置:手工配置,比如ID生成策略
代码生成器实现
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
// globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOutputDir("D:/code/mybatisplus/mybatisplus-04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("Jen"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}