目录
MyBatis-Plus快速入门
简介
快速入门
MyBatis-Plus核心功能
基于Mapper接口 CRUD
对比mybatis和mybatis-plus:
CRUD方法介绍:
基于Service接口 CRUD
对比Mapper接口CRUD区别:
为什么要加强service层:
使用方式
CRUD方法介绍:
分页查询实现
MyBatis和Mybatis-Plus分页查询
使用分页查询
自定义的mapper方法使用分页
条件构造器使用
1、条件构造器作用
2、条件构造器继承结构
3、基于QueryWrapper组装条件
4、基于UpdateWrapper组装条件
5、基于LambdaQueryWrapper组装条件
LambdaQueryWrapper对比QueryWrapper
6、基于LambdaUpdateWrapper组装条件
核心注解使用
@TableName
@TableId
@TableField
MyBatis-Plus高级拓展
逻辑删除实现
逻辑删除全局配置:
乐观锁实现
场景:
资源争抢的解决方法:
理解点:
具体技术和方案:
版本号乐观锁的实现流程:
使用mybatis-plus数据使用乐观锁:
防全表更新和删除实现
MyBatis-Plus代码生成器(MyBatisX 插件)
MyBatisX插件逆向工程
MyBatisX快速代码生成
MyBatis-PlusMyBatis-Plus 官方文档https://baomidou.com/?spm=wolai.workspace.0.0.330e767bDepZBf
MyBatis-Plus快速入门
简介
MyBatis-Plus(MP)是一个MyBatis的增强工具,在MyBatis的基础上做增强,简化开发。
支持MySQL,Oracle等大部分数据库。
MyBatis-Plus的功能总的来说就是:
自动生成单表的CRUD功能
提供丰富的条件拼接方法
全自动ORM类型持久层框架
(全自动orm思维持久层框架如hibernate,只需要把数据库数据和java实体类映射(配置),它就能提供crud方法,而且会自动生成对应的sql语句。)
MyBatis-Plus的功能仅限于单表操作,多表操作还是自己写SQL比较好。
快速入门
1.准备数据库表:database:mybatis_plus,table:user
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');
2.创建boot工程:springboot-mybatis-plus
3.导入依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> </parent> <groupId>com.qiu</groupId> <artifactId>springboot-mybatis-plus</artifactId> <version>1.0-SNAPSHOT</version> <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> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- 数据库相关配置启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- druid启动器的依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.18</version> </dependency> <!-- 驱动类--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> </dependency> </dependencies> <!-- SpringBoot应用打包插件--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
(如果druid版本在1.2.20以下会报错,解决方法在之前springboot文章中)
4.创建pojo类
@Data public class User { private Long id; private String name; private Integer age; private String email; }
5.创建mapper接口,并继承mybatis-plus提供的Mapper接口,此接口下自带crud方法
public interface UserMapper extends BaseMapper<User> { }
6.编写测试类进行测试(导入了spring-boot-starter-test启动器,只需要在测试类添加@SpringBootTest就可以直接注入组件)
@SpringBootTest//springboot下测试环境注解 public class BootTest { @Autowired private UserMapper userMapper; @Test public void query(){ List<User> users = userMapper.selectList(null); System.out.println("users = " + users); } }
注意:使用注解时,测试类要和启动类在同一目录结构下
7.测试结果
MyBatis-Plus核心功能
基于Mapper接口 CRUD
对比mybatis和mybatis-plus:
mybatis对数据库的操作:1.创建mapper接口定义crud方法 2.创建mapperxml编写crud的sql
mybatis-plus对数据库的操作:1.创建mapper接口继承BaseMapper<T>
2.原来的mybatis操作没有变,需要时依然可以使用
BaseMapper中的方法。
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteById(T entity);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> queryWrapper);
int deleteBatchIds(@Param("coll") Collection<?> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {
List<T> list = this.selectList(queryWrapper);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0L;
}
Long selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
}
CRUD方法介绍:
application.yaml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台输出日志
#像驼峰映射的操作在底层已经设置了好了,可以不修改。
mapper接口:
public interface UserMapper extends BaseMapper<User> {
}
测试:
@SpringBootTest//springboot下测试环境注解
public class BootTest {
@Autowired
private UserMapper userMapper;
// +---------------------+--------+------+--------------------+
// | id | name | age | email |
// +---------------------+--------+------+--------------------+
// | 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 |
// | 1767361818149871618 | 123 | 88 | 11 |
// +---------------------+--------+------+--------------------+
@Test
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
// 1767362650761154561(Long), 123(String), 88(Integer), 11(String)
// mybatis-plus会自动对id进行赋值。
public void test_insert(){
User user = new User();
user.setAge(88);
user.setEmail("11");
user.setName("123");
userMapper.insert(user);
}
// +---------------------+--------+------+--------------------+
// | id | name | age | email |
// +---------------------+--------+------+--------------------+
// | 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 |
// | 1767361818149871618 | 123 | 88 | 11 |
// | 1767362650761154561 | 123 | 88 | 11 |
// +---------------------+--------+------+--------------------+
@Test
public void test_delete(){
//根据id删除
// DELETE FROM user WHERE id=?
// 1767362650761154561(Long)
userMapper.deleteById(1767362650761154561L);
//根据age=20和name=jack
// DELETE FROM user WHERE name = ? AND age = ?
// jack(String), 20(Integer)
Map map=new HashMap();
map.put("age",20);
map.put("name","jack");
userMapper.deleteByMap(map);
}
// +---------------------+--------+------+--------------------+
// | id | name | age | email |
// +---------------------+--------+------+--------------------+
// | 1 | Jone | 18 | test1@baomidou.com |
// | 3 | Tom | 28 | test3@baomidou.com |
// | 4 | Sandy | 21 | test4@baomidou.com |
// | 5 | Billie | 24 | test5@baomidou.com |
// | 1767361818149871618 | 123 | 88 | 11 |
// +---------------------+--------+------+--------------------+
@Test
public void test_update(){
//根据id修改
//user的id必须有值
//如果setXxx(null)不进行修改,这就是为什么要把age设置为Integer包装类,因为int类型默认为0
// UPDATE user SET age=? WHERE id=?
// 99(Integer), 1(Long)
User user =new User();
user.setId(1L);
user.setName(null);
user.setAge(99);
userMapper.updateById(user);
//将所有人的年龄改为Name改为111
//如果setXxx(null)不进行修改
// UPDATE user SET name=?
// 111(String)
User user1 = new User();
user1.setName("111");
user1.setEmail(null);
userMapper.update(user1,null);
}
// +---------------------+------+------+--------------------+
// | id | name | age | email |
// +---------------------+------+------+--------------------+
// | 1 | 111 | 99 | test1@baomidou.com |
// | 3 | 111 | 28 | test3@baomidou.com |
// | 4 | 111 | 21 | test4@baomidou.com |
// | 5 | 111 | 24 | test5@baomidou.com |
// | 1767361818149871618 | 111 | 88 | 11 |
// +---------------------+------+------+--------------------+
@Test
public void test_select(){
//根据id查询
// SELECT id,name,age,email FROM user WHERE id=?
// 1(Long)
// user = User(id=1, name=111, age=99, email=test1@baomidou.com)
User user = userMapper.selectById(1L);
System.out.println("user = " + user);
//集合查询
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
// 1(Long), 3(Long)
// users = [User(id=1, name=111, age=99, email=test1@baomidou.com),
// User(id=3, name=111, age=28, email=test3@baomidou.com)]
List<Long> ids = new ArrayList<>();
ids.add(1L);ids.add(3L);
List<User> users = userMapper.selectBatchIds(ids);
System.out.println("users = " + users);
}
基于Service接口 CRUD
对比Mapper接口CRUD区别:
1.service层的加强进一步封装了crud,采用 get 查询单行, remove 删除, list 查询集合, page 分页 前缀命名方式区分 Mapper 层避免混淆,并且支持批量操作
2.service层的方法自动添加事务
为什么要加强service层:
1.如果接收的请求是查询用户信息,那么service层的操作仅仅只是调用了mapper的select方法。
2.如果controller层接收的请求较简单(插入单表数据,查询单表数据等),可以直接调用service层加强的方法,不需要在从service层调用mapper层方法。
3.加强service后,简单逻辑走service,复杂逻辑再走mapper
使用方式
接口继承IService<T>接口
public interface UserService extends IService<User> {
}
实现类继承 ServiceImpl<M extends BaseMapper<T>, T>
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
为什么既要继承接口又要继承实现类?
IService<T>接口中定义了所有方法,但只默认实现了一半,而ServiceImpl<M extends BaseMapper<T>, T>中实现了另一半,如果不继承实现类,需要自己手动实现另一半方法。(ServiceImpl 实现了 IService)
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
……
}
CRUD方法介绍:
package com.qiu;
import com.qiu.pojo.User;
import com.qiu.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class BootUserTest2 {
@Autowired
private UserService userService;
// 保存
@Test
public void test_save(){
List<User> list= new ArrayList<>();
User user=new User();
user.setAge(18);
user.setEmail("666");
user.setName("123");
list.add(user);
User user1=new User();
user1.setAge(23);
user1.setEmail("777");
user1.setName("231");
list.add(user1);
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
// 1767385817043038210(Long), 123(String), 18(Integer), 666(String)
// 1767385817126924289(Long), 231(String), 23(Integer), 777(String)
userService.saveBatch(list);
}
// 保存或修改:如果user的id有值则修改,没有则保存
@Test
public void test_saveOrUpdate(){
// 添加
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
// 1767386132609855489(Long), 9999(String), 998(Integer), 999(String)
User user=new User();
user.setAge(998);
user.setEmail("999");
user.setName("9999");
userService.saveOrUpdate(user);
// 修改
// SELECT id,name,age,email FROM user WHERE id=?
// 1, 111, 99, test1@baomidou.com
// UPDATE user SET name=?, age=?, email=? WHERE id=?
// 1999(String), 1999(Integer), 1999(String), 1(Long)
User user1=new User();
user1.setId(1L);
user1.setAge(1999);
user1.setEmail("1999");
user1.setName("1999");
userService.saveOrUpdate(user1);
}
// 修改
// UPDATE user SET name=?, age=?, email=? WHERE id=?
// 8888(String), 8888(Integer), 8888(String), 1(Long)
@Test
public void test_update(){
User user=new User();
user.setId(1L);
user.setAge(8888);
user.setEmail("8888");
user.setName("8888");
userService.updateById(user);
}
// 移除
// DELETE FROM user WHERE id=?
// 1767386132609855489(Long)
@Test
public void test_remove(){
userService.removeById(1767386132609855489L);
}
// 查询
@Test
public void test_getOrList(){
// SELECT id,name,age,email FROM user WHERE id=?
// 1(Long)
// user = User(id=1, name=8888, age=8888, email=8888)
User user = userService.getById(1L);
System.out.println("user = " + user);
// SELECT id,name,age,email FROM user
// list = [User(id=1, name=8888, age=8888, email=8888), User(id=3, name=111, age=28, email=test3@baomidou.com), User(id=4, name=111, age=21, email=test4@baomidou.com),
// User(id=5, name=111, age=24, email=test5@baomidou.com), User(id=1767361818149871618, name=111, age=88, email=11), User(id=1767385817043038210, name=123, age=18, email=666),
// User(id=1767385817126924289, name=231, age=23, email=777), User(id=1767386445735616513, name=9999, age=998, email=999), User(id=1767386600643825666, name=9999, age=998, email=999)]
List<User> list = userService.list(null);
System.out.println("list = " + list);
}
}
分页查询实现
在mapper层增强和service层增强中都提供了分页查询方法。
MyBatis和Mybatis-Plus分页查询
MyBatis:1、设置分页参数PageHelper
2、编写sql语句
3、结果封装PageInfo
4、获取分页数据
原理:后置拦截器,sql语句不要分号(;)结尾,因为分页查询底层就是在sql语句后拼接字符串(limit x,y)。
Mybatis-Plus:在MyBatis-Plus中一个插件集合 MybatisPlusInterceptor ,只需要把分页插件放到集合中进行了。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//注意DbType.MYSQL导包别导错
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
使用分页查询
@Test
public void test_page(){
//设置分页参数
Page<User> page = new Page<>(1,5);
//查询后数据会写入原来的page,可以使用它获取数据
userService.page(page,null);
//获取数据
List<User> list = page.getRecords();
//打印数据
list.forEach(System.out::println);
System.out.println(" 获取当前页: " + page.getCurrent());
System.out.println(" 每页显示的条数: " + page.getSize());
System.out.println(" 总数据数: " + page.getTotal());
System.out.println(" 总页数: " + page.getPages());
System.out.println(" 是否有上一页: " + page.hasPrevious());
System.out.println(" 是否有下一页: " + page.hasNext());
}
// SELECT COUNT(*) AS total FROM user
// SELECT id,name,age,email FROM user LIMIT ?
// 5(Long)
// User(id=1, name=8888, age=8888, email=8888)
// User(id=3, name=111, age=28, email=test3@baomidou.com)
// User(id=4, name=111, age=21, email=test4@baomidou.com)
// User(id=5, name=111, age=24, email=test5@baomidou.com)
// User(id=1767361818149871618, name=111, age=88, email=11)
// 获取当前页: 1
// 每页显示的条数: 5
// 总数据数: 9
// 总页数: 2
// 是否有上一页: false
// 是否有下一页: true
自定义的mapper方法使用分页
方法:UserMapper接口
public interface UserMapper extends BaseMapper<User> {
// 参数列表携带IPage接口
// 返回结果为IPage
IPage queryPageByAge(IPage<User> page, @Param("age") Integer Age);
}
实现:MapperXml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qiu.mapper.UserMapper">
<!-- 记得起别名,sql后面不要加分号-->
<select id="queryPageByAge" resultType="user">
select * from user where age > #{age}
</select>
</mapper>
测试方法:
@Test
public void test_page_age(){
Page page =new Page<User>(1,3);
userMapper.queryPageByAge(page,22);
List records = page.getRecords();
records.forEach(System.out::println);
}
结果:
条件构造器使用
1、条件构造器作用
Mybatis-Plus的条件构造器可以让我们构建灵活、高效的查询条件。
场景:删除 name=John,age != 30,email like %@gmail.com
@Test
public void test2(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","John");
queryWrapper.ne("age",30);
queryWrapper.like("email","@gmail.com");
// 上面操作等于delete from user where name = "John" and age !=30 and email like "%@gmail.com%"
// int delete(@Param("ew") Wrapper<T> queryWrapper);
userMapper.delete(queryWrapper);
}
2、条件构造器继承结构
Wrapper:抽象类,顶端父类
一般操作UpdateWrapper、QueryWrapper、LambdaUpdateWrapper和LambdaQueryWrapper,推荐使用后两个。
UpdateWrapper和LambdaUpdateWrapper:一般修改时使用
QueryWrapper和LambdaQueryWrapper:一般删除和查询时使用
3、基于QueryWrapper组装条件
@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;
@Test
//组装查询条件
public void test1(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.like("name",'a')
.between("age",20,30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
}
@Test
// 组装排序条件
public void test2(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(queryWrapper);
}
@Test
// 组装删除条件
public void test3(){
//删除email为空的用户
//DELETE FROM t_user WHERE (email IS NULL)
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
}
@Test
// and和or关键字使用(修改):
public void test4(){
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
// 优先级:not>and>or
//UPDATE t_user SET age=?, email=? WHERE username LIKE ? AND age > ? OR email IS NULL
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("name","a")
.or().isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("user@atguigu.com");
int result = userMapper.update(user, queryWrapper);
}
@Test
// 指定列映射查询:返回指定的列数据
public void test5(){
//查询用户信息的username和age字段
//SELECT username,age FROM t_user
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age");
//selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
@Test
// condition判断组织条件
public void test6(){
String name = "John";
int age = 18;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//判断条件拼接
//当name不为null拼接等于, age > 1 拼接等于判断
//方案1: 手动判断
// import org.junit.platform.commons.util.StringUtils;
if (StringUtils.isNotBlank(name)){
queryWrapper.eq("name",name);
}
if (age > 1){
queryWrapper.eq("age",age);
}
//方案2: 拼接condition判断
//每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
//eq(condition,列名,值)
queryWrapper.eq(StringUtils.isNotBlank(name),"name",name)
.eq(age>1,"age",age);
}
}
4、基于UpdateWrapper组装条件
@Test
public void test(){
// 如果使用QueryWrapper进行修改,需要准备要修改的实体类,而且数据不能改为null值。
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",18);
User user = new User();
user.setAge(99);
user.setName(null);//此时不会修改name
userMapper.update(user,queryWrapper);
// 使用UpdateWrapper修改
// 可以直接携带数据 updateWrapper.set("age",12);
// 也可以设置为空 updateWrapper.set("email",null);
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("age",35)
.set("email",null)
.set("age",19);
userMapper.update(null,updateWrapper);
}
5、基于LambdaQueryWrapper组装条件
LambdaQueryWrapper对比QueryWrapper
QueryWrapper表示字段名时使用字符串:queryWrapper.eq("name","qiu");
LambdaQueryWrapper表示字段名时使用实体类的属性引用:lambdaQueryWrapper.eq(User::getName,"qiu");
提高了代码的可读性和可维护性。
@Test
//组装查询条件
public void test1(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
LambdaQueryWrapper<User> lambdaQueryWrapper=new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(User::getName,'a')
.between(User::getAge,20,30)
.isNotNull(User::getEmail);
List<User> list = userMapper.selectList(lambdaQueryWrapper);
}
6、基于LambdaUpdateWrapper组装条件
@Test
public void test2(){
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.gt(User::getAge,35)
.set(User::getEmail,null)
.set(User::getAge,19);
userMapper.update(null,updateWrapper);
}
核心注解使用
@TableName
@TableName("value"):加到与数据库表对应的pojo类上,value对应的数据库的表名,可以不加,不加默认使用实体类的名字作为表名,忽略大小写。
IService<User>,BaseMapper<User>等会根据User类对应的表名在数据库中操作相应的表。
一般数据库表会有 “ t_ ”之类的前缀,可以在application.yaml中添加
mybatis-plus.global-config.db-config.table-prefix: t_
来统一配置,如果类上有@TableName注解则该配置对此类无效。
@TableId
@TableId(value="主键名", type=主键策略)
使用场景:1.主键的列名与属性名不一致(驼峰映射后能对应上的不需要使用)
2.指定插入数据时如何生成主键,常用枚举类型如下:
- ASSIGN_ID:分配ID,在mybati-plus3.3.0以后会使用雪花算法分配id(Long类型)
- AUTO:数据库ID自增(需要mysql数据库表的主键列设置自增长)
主键策略也可以统一设置:application.yaml中的mybatis-plus.global-config.db-config.id-type: auto
@TableField
当普通字段与属性不一致时,可以使用该注解手动设置。
@Data
public class User {
@TableId("id")
private Long userId;
@TableField("name")
private String userName;
private Integer age;
private String email;
@TableField(exist = false)
private String py;
}
pojo类属性应该与数据库表的字段一一对应,属性缺少不报错但不会返回该字段,属性比字段多会报错,要么删除,要么该属性上使用@TableField(exist=false)。
MyBatis-Plus高级拓展
逻辑删除实现
逻辑删除是指在表中添加一个字段模拟删除,在删除操作时使用假删除,方便之后进行数据分析和恢复。
在数据库表中添加删除字段
alter table user add deleted int default 0 ; int类型,1为逻辑删除,0为未逻辑删除。
在pojo类中添加逻辑删除属性,并在属性上添加@TableLogic注解
使用该注解后,默认执行mybatis-plus提供的删除方法时,会自动换成修改方法,如果要删除的数据中逻辑删除字段为0,就将该字段改为1,不为0则不修改。
逻辑删除全局配置:
- 在application.yaml中设置mybatis-plus.global-config.db-config.logic-delete-field: deleted 设置所有逻辑这段的实体字段名为deleted。
- mybatis-plus.global-config.db-config.logic-delete-value: 1 设置逻辑删除值为1
- mybatis-plus.global-config.db-config.logic-not-delete-value: 0 设置未逻辑删除值为0
乐观锁实现
场景:
可能为500,也可能为0。
两个动作都是先取值在减值,如果两个动作同时取到1000,减值后为500。
资源争抢的解决方法:
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
悲观锁:获取资源时把资源上锁,确保在减值操作前只有自己获得资源,但效率低。
乐观锁:获取资源前检查是否资源已被使用,没有就操作资源,有则等待一段时间后继续尝试获取资源。效率较高,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。
理解点:
悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!
具体技术和方案:
1.乐观锁实现方案和技术:
- 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,一致则更新成功,否则表示数据已被修改,需要冲突处理。
- CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
- 无锁数据结构:采用无锁数据结构,如无锁队列,无锁哈希表等,通过使用原子操作实现并发安全。
2.悲观锁实现方案和技术:
- 锁机制:使用传统的锁机制,如互斥锁或读写锁来保证对共享资源的独占访问。
- 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
- 信号量:使用信号量来限制对资源的并发访问。
版本号乐观锁的实现流程:
1.数据库表添加一个字段version
2.取出数据时,获取当前version
3.更新时,检查获取版本号是不是数据库当前最新版本号
4.如果是,说明没人修改数据,执行更新操作,set 数据更新,version = version + 1
5.如果不是,说明数据被修改了,当前数据为无效数据,更新失败。
使用mybatis-plus数据使用乐观锁:
1. 添加版本号更新插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 版本号更新插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
2.乐观锁字段添加@Version注解
数据库添加乐观锁字段
ALTER TABLE t_user ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
表结构
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | bigint | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| age | int | YES | | NULL | |
| email | varchar(20) | YES | | NULL | |
| deleted | int | YES | | 0 | |
| VERSION | int | YES | | 1 | |
+---------+-------------+------+-----+---------+-------+
乐观锁字段支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
仅支持updateById(id)与update(entry, wrapper)方法
@Data
public class User {
@TableId("id")
private Long userId;
@TableField("name")
private String userName;
private Integer age;
private String email;
// @TableLogic
逻辑删除字段 int mybatis-plus下,默认逻辑删除为1,未逻辑删除为0
private Integer deleted;
@Version
private Integer version;
}
测试:
//演示乐观锁生效场景
@Test
public void testQuick7(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(4);
User user1 = userMapper.selectById(4);
user.setAge(20);
user1.setAge(30);
userMapper.updateById(user);
//乐观锁生效,失败!
userMapper.updateById(user1);
}
结果:
+---------------------+--------+------+--------------------+---------+---------+
| id | name | age | email | deleted | VERSION |
+---------------------+--------+------+--------------------+---------+---------+
| 1 | Jone | 18 | test1@baomidou.com | 0 | 1 |
| 2 | Jack | 20 | test2@baomidou.com | 0 | 1 |
| 3 | Tom | 28 | test3@baomidou.com | 0 | 1 |
| 4 | Sandy | 20 | test4@baomidou.com | 0 | 2 |
| 5 | Billie | 24 | test5@baomidou.com | 1 | 1 |
| 1767361818149871618 | 111 | 88 | 11 | 0 | 1 |
| 1767385817043038210 | 123 | 18 | 666 | 0 | 1 |
| 1767385817126924289 | 231 | 23 | 777 | 0 | 1 |
| 1767386445735616513 | 9999 | 998 | 999 | 0 | 1 |
| 1767386600643825666 | 9999 | 998 | 999 | 0 | 1 |
+---------------------+--------+------+--------------------+---------+---------+
防全表更新和删除实现
添加防止全表更新或删除插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 版本号更新插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新或删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
测试:
@Test
public void test(){
// 全表删除
userMapper.delete(null);
}
结果:报错
MyBatis-Plus代码生成器(MyBatisX 插件)
MyBatisX插件逆向工程
MyBatisX快速代码生成
使用mybatisX插件,自动生成sql语句实现
MybatisX快速开发插件 | MyBatis-PlusMyBatis-Plus 官方文档https://baomidou.com/pages/ba5b24/?spm=wolai.workspace.0.0.330e767bDepZBf#%E5%8A%9F%E8%83%BD