MyBatis-Plus入门学习
- 简介
- 特性
- 快速开始
- MyBatis-Plus的注解详解
- @Tableld
- 主键生成策略
- 1、数据库自动增长 AUTO
- 2、UUID
- 3、Redis生成id
- 4、MP主键自动生成
- @TableName
- @TableField
- 自动填充
- 测试方法:
- update
- 乐观锁
- select
- 查所有
- 根据id查
- 多个id批量查询
- 简单条件查询(通过map封装条件)
- 分页
- delete
- 根据id删除
- 批量删除
- 简单的条件查询删除
- 逻辑删除
- 性能分析
- Wrapper
- wrapper条件构造器测试
- 1、ge、gt、le、lt、isNull、isNotNull
- 2、eq、ne
- 3、between、notBetween
- 4、allEq
- 5、like、notLike、likeLeft、likeRight
- 6、in、notIn、inSql、notInSql、exists、notExists
- 7、or、and
- 8、嵌套or、嵌套and
- 9、orderBy、orderByDesc、orderByAsc
- 10、last
- 11、set、setSql
简介
MyBatis-Plus是MyBatis的增强工具,在MyBatis的基础上做出增强改变,简化开发,提高效率
mybatis-plus的官网链接
特性
快速开始
第一步:创建一张表
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
第二步:插入对应数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
第三步:初始化一个项目工程
第四步:引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0+ 版本</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
第五步:配置数据库连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-plus?
serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
第五步:在SpringBoot项目的启动类中添加**@MapperScan注解**,用于对mapper的扫描
package com.example.mpdemo001;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mpdemo001.mapper")
public class Mpdemo001Application {
public static void main(String[] args) {
SpringApplication.run(Mpdemo001Application.class, args);
}
}
第六步:编码
实体类:
package com.example.mpdemo001.bean;
import lombok.Data;
/**
* @Title: User
* @Package com.example.mpdemo001.bean
* @Author: CXY
* @Copyright CXY
* @CreateTime: 2023/3/9 12:35
*/
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper接口(MyBatis-Plus不需要在接口中写很多方法,只需要继承 BaseMapper< T>即可,T表示泛型)
package com.example.mpdemo001.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo001.bean.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
**注意:**在mapper接口上面需要添加一个@Mapper的注解,用于将Mapper接口加入Spring管理
第七步:测试
package com.example.mpdemo001;
import com.example.mpdemo001.bean.User;
import com.example.mpdemo001.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
public class Mpdemo001ApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void contextLoads() {
List<User> users = userMapper.selectList(null);
for (User user:users
) {
System.out.println(user);
}
}
}
UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件
测试结果:
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
可以配置MyBatis-Plus的日志文件,这样可以看到详细的SQL语句实现过程
日志文件的配置如下:
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
MyBatis-Plus的注解详解
@Tableld
IdType:顾名思义即表示主键的类型
主键生成策略
参考资料:分布式系统唯一ID生成方案汇总:https://www.cnblogs.com/haoxinyue/p/5208136.htm
1、数据库自动增长 AUTO
这个是指在建表的时候可以直接设置id自增,这样的话idType即为AUTO;
**缺点:**这样id自增的主键,不利于数据库的分表实现,因为id是按顺序递增的,所以每一次分表的时候都需要知道前一张表的最后一个id的值,再那个基础上进行id加一的操作
2、UUID
每次生成随机唯一的值
**优点:**每次分表的时候不需要知道上一张表的最后一个元素的id值
**缺点:**id无法进行排序
3、Redis生成id
Redis是单线程的,所以也可以用来生成全局唯一的id,可以用Redis的原子操作INCR和INCRBY来实现
但是使用这个来进行id生成的时候,需要指定初始值和步长
使用Redis集群可以防止单点故障的问题
单点故障:(英语:single point of failure,缩写SPOF)是指系统中一点失效,就会让整个系统无法运作的部件,换句话说,单点故障即会整体故障。
**优点:**不依赖于数据库,灵活方便,且性能优于数据库,数字id天然排序,对分页或者需要排序的结果很有帮助
**缺点:**如果系统中没有Redis,还需引入新的主键,增加系统的复杂度;需要编码和配置的工作量比较大
4、MP主键自动生成
mp可以通过雪花算法自动生成一个19位数字的id,
@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
@TableField
- 描述:字段注解(非主键)
自动填充
将数据自动填充进去
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。 我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作
具体实现过程:
在实体类中需要自动进行自动填充的对象上面添加注解:**@TableField(fill=FieldFill.Xxx)**FieldFill的取值:
然后创建一个类用于实现MetaObjectHandler接口,实现接口中的方法,
注意:需要在这个类上面添加注解,让Spring进行管理
package com.example.mpdemo001.bean;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
/**
* @Title: MyBatisMeta
* @Package com.example.mpdemo001.bean
* @Author: CXY
* @Copyright CXY
* @CreateTime: 2023/3/10 14:02
*/
@Component
//这个不能忘,忘了就无法进行了
public class MyBatisMeta implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//插入时间,第一次创建的时候,需要插入创建时间和更新时间
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//每一次更新数据的时候,插入时间
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
测试结果:
测试方法:
update
@Test
public void testUpdateById(){
User user=new User();
user.setId(1L);
user.setName("战三");
int i = userMapper.updateById(user);
System.out.println(i);
}
结果:
注意:update生成的是动态sql
乐观锁
针对一种特定问题的解决方案,主要解决的是丢失更新的问题
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数 据更新
如果不考虑事务的隔离性的话,会产生的问题有:
1、产生读问题:脏读,不可重复读,幻读
2、产生写问题:丢失更新(就是指多个人同时修改同一条数据,最后提交的把之前提交的数据覆盖了)
解决丢失更新问题:
悲观锁:串行,只有一个人能在当前情况下进行操作
乐观锁:添加了一个version版本号,当一个人在进行数据操作的时候,提交数据时会比较当前数据版本号和数据库中的版本号是否一直,如果一致则可以进行修改,修改后版本号进行加一的操作。反之不能进行修改。
**乐观锁的使用例子有:**12306抢票系统,当别人把最后一张票抢走之后,另外的人就不能再进行抢票了
乐观锁的使用流程:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
当有很多配置的时候,建议新建一个配置类,将所有配置写在配置类中
package com.example.mpdemo001.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Title: MyBatisConfigurations
* @Package com.example.mpdemo001.config
* @Author: CXY
* @Copyright CXY
* @CreateTime: 2023/3/10 15:12
*/
@Configuration
@MapperScan("com.example.mpdemo001.mapper")
public class MyBatisConfigurations {
/**
* 乐观锁插件
*
* 新版
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
测试成功代码:
@Test
public void testOptimisticLockerFail() {
User user = userMapper.selectById(1l);
user.setName("死亡法师");
user.setAge(10000);
int i = userMapper.updateById(user);
System.out.println(i);
}
测试成功的结果
测试失败代码:
@Test
public void testOptimisticLockerFail() {
User user = userMapper.selectById(2l);
user.setName("哈利波特");
user.setAge(300);
//模拟取出数据后,数据库中version实际数据比取出的值大,即已被其它线程修改并更新了version
user.setVersion(user.getVersion()-1);
int i = userMapper.updateById(user);
System.out.println(i);
}
结果:
select
查所有
@Test
public void testSelect(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
结果
根据id查
多个id批量查询
简单条件查询(通过map封装条件)
注意:map中的key对应的是数据库中的列名。例如数据库user_id,实体类是userId,这时map的key需 要填写user_id
分页
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
添加插件:
/**
* 分页插件(官网最新)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor1() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
测试分页实现:
@Test
public void testPageSelect(){
Page page=new Page<>(1,3);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getSize());
System.out.println(page.getTotal());
System.out.println(page.getRecords());
}
测试结果
分页的各个属性:
delete
根据id删除
@Test
public void testDeletedById(){
int i = userMapper.deleteById(4l);
System.out.println(i);
}
批量删除
@Test
public void testDeletedByIds(){
int i = userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
System.out.println(i);
}
简单的条件查询删除
@Test
public void testDeleteByMap(){
HashMap<String,Object> hashMap=new HashMap<>();
hashMap.put("name","Tom");
hashMap.put("age",28);
int i = userMapper.deleteByMap(hashMap);
System.out.println(i);
}
逻辑删除
逻辑删除就是指在数据中多加一个标志属性,删除的时候只对该属性进行修改,而不是真正的删除数据,逻辑删除有利于数据的恢复
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍 旧能看到此条数据记录
在实体类上添加deleted属性
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
如果想修改默认属性值,则可在application.properties中进行配置
默认0表示未被删除,1表示被删除
在配置类中注册bean
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
高版本不用配置插件了,上述java代码无需编写。
测试删除:
@Test
public void testLogicDelete() {
int result = userMapper.deleteById(1L);
System.out.println(result);
}
被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操做
性能分析
在配置类中添加性能优化插件
@Profile({“dev”,“test”})表示对什么环境起作用
dev:开发环境
test:测试环境
prod:生产环境,项目部署好之后的环境 最好不要在这里使用性能优化插件
环境配置在配置文件中设置:
spring.profile.active=xxx(dev,test,prod)
**参数:**maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。
**参数:**format: SQL是否格式化,默认false
Wrapper
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : Entity 对象封装操作类,不是用lambda语法(使用较多)
- UpdateWrapper : Update 条件封装,用于Entity对象更新操作
- AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
- LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
wrapper条件构造器测试
1、ge、gt、le、lt、isNull、isNotNull
@Test
public void testQueryWrapper(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper
.isNull("email")
.ge("age",30)
.isNotNull("name");
//上面代码可以表示多个条件
int delete = userMapper.delete(queryWrapper);
System.out.println(delete);
}
gt:大于;ge:大于等于;le:小于;lt:小于等于
2、eq、ne
eq表示等于
ne表示不等于
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
注意:selectOne方法返回的是一条实体记录,如果返回多条时,会报错
3、between、notBetween
是包含边界值的查询条件
需要注意小值在前,大值在后
@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 20, 30);
Long count = userMapper.selectCount(queryWrapper);
System.out.println("==============="+count);
}
4、allEq
多条件均相等
@Test
public void testSelectList() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "Jack");
map.put("age", 20);
queryWrapper.allEq(map);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
5、like、notLike、likeLeft、likeRight
like:模糊查询,表示like %内容%
notLike:不包含
likeLeft:表示like %内容
likeRight:表示 like 内容%
@Test
public void testQuerrySelectMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.notLike("name","san")
.likeRight("email","test1");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
6、in、notIn、inSql、notInSql、exists、notExists
in notIn用法如下:
notIn(“age”,{1,2,3})—>age not in (1,2,3)
notIn(“age”, 1, 2, 3)—>age not in (1,2,3)
inSql、notInSql可以实现子查询
@Test
public void testSelectObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//queryWrapper.in("id", 1, 2, 3);
queryWrapper.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
objects.forEach(System.out::println);
}
7、or、and
不调用or则默认为使用 and 连
8、嵌套or、嵌套and
这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号
@Test
public void testUpdate2() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or(i -> i.eq("name", "李白").ne("age", 20));
// i -> i.eq("name", "李白").ne("age", 20)这一段是lambda表达式
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
最后的sql语句为:
UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ?
OR ( name = ? AND age <> ?)
9、orderBy、orderByDesc、orderByAsc
**orderBy:**默认是升序
**orderByDesc:**表示升序排列,该方法可以有多个条件参与排列
**orderByAsc:**表示降序排列,该方法可以有多个条件参与排列
10、last
直接拼接到 sql 的最后
注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
@Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
sql语句:SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 limit 1
11、set、setSql
最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的字段
@Test
public void testUpdateSet() {
//修改值
User user = new User();
user.setAge(99);
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
.setSql(" email = '123@qq.com'");//可以有子查询
int result = userMapper.update(user, userUpdateWrapper);
}
sql语句:
UPDATE user SET age=?, update_time=?, name=?, email = ‘123@qq.com’ WHERE deleted=0 AND name LIKE ?
b站教学视频:
尚硅谷