官网:https://baomidou.com/
MyBatis-Plus (简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis-Plus提供了通用的mapper和service,可以在不编写任何SQL语句的情况下,快速的实现对单表的CRUD批量、逻辑删除、分页等操作。
任何能使用 MyBatis
进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
IDE: IDEA 2023.1
JDK:JDK8+
构建工具:maven3.8.3
MySQL版本:MySQL5.5.27
SpringBoot:2.7.6
mybatis-plus:3.5.3.1
-- 创建数据库
CREATE DATABASE mybatisplus;
-- 使用数据库
USE mybatisplus;
-- 创建表
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, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
使用Spring Initializr
快速构建SpringBoot项目
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.6version>
<relativePath/>
parent>
<groupId>com.qbzaixiangroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demoname>
<description>demodescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.34version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
spring:
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库的驱动
driver-class-name: com.mysql.jdbc.Driver
# 配置连接数据库的url
url: jdbc:mysql://127.0.0.1:3306/mybatisplus?characterEncoding=utf-8&useSSL=false
# 配置连接数据库的账号
username: root
# 配置连接数据库的密码
password: 123
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mybatis-plus提供了BaseMapper接口,其中提供大量的CRUD的方法,我们的接口只需要去继承这个接口,基本就可以实现对单表的CRUD操作。接口的泛型对应编写的实体类
public interface UserMapper extends BaseMapper<User> {
}
在 Spring Boot 启动类中添加
@MapperScan
注解,扫描 Mapper 文件夹:
@SpringBootApplication
@MapperScan("com.qbzaixian.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
UserMapper 中的
selectList()
方法的参数为 MP 内置的条件封装器Wrapper
,所以不填写就是无任何条件
@SpringBootTest
public class UserTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect(){
// 接口中提供的selectList参数为条件,如果没有设置为null
List<User> users = this.userMapper.selectList(null);
users.forEach(System.out::println);
}
}
这里我选择降低SpringBoot的版本为2.7.6来解决这个问题。
<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.7.6version> <relativePath/>
运行测试类,看到如下测试结果
通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!
从以上步骤中,我们可以看到集成
MyBatis-Plus
非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。
希望看到mybatis-plus执行的具体过程和对应的sql语句,需要简单的配置mybatis-plus的日志即可
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus提供了BaseMapper接口,其中提供大量的CRUD的方法,我们的接口只需要去继承这个接口,基本就可以实现对单表的CRUD操作。接口的泛型对应编写的实体类
参数说明
类型 参数名 描述 T entity 实体对象 // 插入一条记录 int insert(T entity);
// BaseMapper的新增功能
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
@Test
public void testInset(){
User user = new User();
user.setAge(20);
user.setName("赵四");
user.setEmail("[email protected]");
int result = this.userMapper.insert(user);
System.out.println(result);
}
温馨提示:添加完成之后,添加的新对象也会进行ID回显的,但ID值默认采用的是雪花算法计算出来的数据,不是一个自增长的值。
参数说明
类型 参数名 描述 Wrapper wrapper 实体对象封装操作类(可以为 null) Collection extends Serializable> idList 主键 ID 列表(不能为 null 以及 empty) Serializable id 主键 ID Map columnMap 表字段 map 对象 // 根据 entity 条件,删除记录 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 删除(根据ID 批量删除) int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 ID 删除 int deleteById(Serializable id); // 根据 columnMap 条件,删除记录 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// BaseMapper的删除功能
@Test
public void testDel(){
// 根据id删除 DELETE FROM user WHERE id=?
int i = this.userMapper.deleteById(1L);
// 根据实体对象的id删除 DELETE FROM user WHERE id=?
User user= new User();
user.setId(2L);
int i2 = this.userMapper.deleteById(user);
// 根据Collection集合删除(id列表)
// DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> list = Arrays.asList(2L, 3L, 4L);
this.userMapper.deleteBatchIds(list);
// 根据指定的列属性删除
// DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> map = new HashMap<>();
map.put("age",20);
map.put("name","赵四");
int i3 = this.userMapper.deleteByMap(map);
}
在调用
updateById
方法前,需要在T entity
(对应的实体类)中的主键属性上加上@TableId
注解。参数说明
类型 参数名 描述 T entity 实体对象 (set 条件值,可为 null) Wrapper updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) // 根据 whereWrapper 条件,更新记录 int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper); // 根据 ID 修改 int updateById(@Param(Constants.ENTITY) T entity);
@Test
public void testUpdate(){
// 根据id修改数据 UPDATE user SET name=?, email=? WHERE id=?
User user = new User();
user.setId(5L);
user.setName("乔治");
user.setEmail("[email protected]");
int i = this.userMapper.updateById(user);
}
参数说明
类型 参数名 描述 Serializable id 主键 ID Wrapper queryWrapper 实体对象封装操作类(可以为 null) Collection extends Serializable> idList 主键 ID 列表(不能为 null 以及 empty) Map columnMap 表字段 map 对象 IPage page 分页查询条件(可以为 RowBounds.DEFAULT) // 根据 ID 查询 T selectById(Serializable id); // 根据 entity 条件,查询一条记录 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据ID 批量查询) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 entity 条件,查询全部记录 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据 columnMap 条件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据 Wrapper 条件,查询全部记录 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 entity 条件,查询全部记录(并翻页) IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录(并翻页) IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询总记录数 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
-- 由于前面演示删除,修改等操作,数据库中的数据已经不多了,现在给数据库中重新插入部分数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]');
@Test
public void testSelectAll(){
// selectList:根据条件查询,如果没有条件书写为null
// SELECT id,name,age,email FROM user
List<User> users = this.userMapper.selectList(null);
users.forEach(System.out::println);
// selectById:根据id查询
// SELECT id,name,age,email FROM user WHERE id=?
User user = this.userMapper.selectById(5L);
System.out.println(user);
// selectCount:查询满足条件的记录数,如果没有条件书写为null
// SELECT COUNT( * ) AS total FROM user
Long count = this.userMapper.selectCount(null);
System.out.println(count);
// selectBatchIds:通过多个id列表查询
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
List<User> userList = this.userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
userList.forEach(System.out::println);
// selectByMap:根据指定map中的属性作为查询条件
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String,Object> map = new HashMap<>();
map.put("name","乔治");
map.put("age","20");
List<User> list = this.userMapper.selectByMap(map);
list.forEach(System.out::println);
}
mybatis-plus主要是单表的操作,如果我们需要执行多表,或者执行自己书写的sql脚本与接口,mybatis-plus也支持指定的方式。
在mybaits-plus的
MybatisPlusProperties
类中配置相关的属性配置,其中private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
用来加载指定路径下的xml映射文件的,当然如果你不喜欢这个路径,可以在SpringBoot的配置文件中进行修改
mybatis-plus: config-location: 书写自己的路径
在项目
resoureces
目录下,创建mapper
文件夹,在其中创建UserMapper.xml
文件DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.qbzaixian.mapper.UserMapper"> <select id="selectUserById" resultType="map"> select * from user where id = #{id} select> mapper>
在接口中编写对应的查询方法
public interface UserMapper extends BaseMapper<User> { /** * 根据指定的id,查询user数据,返回map集合 * @param id * @return */ public Map<String,Object> selectUserById(Long id); }
执行查询操作:
@Test
public void testMyQuery(){
// 测试自定义的接口方法
Map<String, Object> map = this.userMapper.selectUserById(1L);
System.out.println(map);
}
mybatis-plus不仅提供通用的mapper接口,还提供通用的Service接口。
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆,T
为任意实体对象IBaseService
继承 Mybatis-Plus
提供的基类Wrapper
为 条件构造器Mybatis-Plus中提供的IService接口和实现类ServiceImpl,封装了常见业务逻辑,可以简单查阅源码
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
// 提供了大量的CRUD相关的方法
}
通过前面的mapper接口的CRUD演示,针对Service接口中的大部分方法使用基本一致,这里就简单演示批量插入操作
虽然有ServiceImpl实现类和IService接口,但是大部分情况下还是需要根据对应的业务书写相关接口和实现类
// 在service包下创建UserService接口
public interface UserService extends IService<User> {
}
// 在service.impl包下创建UserServiceImpl实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
准备进行测试
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserServiceImpl userService;
@Test
public void testSaveBatch(){
List<User> list = new ArrayList<>();
for (int i = 1 ; i < 5 ; i++){
User user = new User();
user.setName("测试"+i);
user.setAge(20+i);
user.setEmail("[email protected]");
list.add(user);
}
// 批量给数据库中插入数据,在mapper接口中是没有的
boolean b = this.userService.saveBatch(list);
System.out.println(b);
}
}
mybatis-plus提供部分的注解,方便快速的进行表、属性、主键、主键生成策略等进行标注
@Data
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
一般用在实体类名与对应的表名不一致的情况下,当然如果项目中整个表名都有对应的前缀,也可以在SpringBoot的核心配置文件进行前缀的配置,省去注解的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 全局配置表名的前缀
global-config:
db-config:
table-prefix: tb_
用于标注当前的实体类的id属性对应表的主键,mybatis-plus默认采用id属性作为主键。
@Data
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
}
TableId注解的两个属性作用:
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
mybatis-plus在设置逐渐的时候ltype属性用来设置主键的生成策略:
@Data @TableName("user") public class User { @TableId(value='对应的表主键列名' , type=主键策略) private Long id; private String name; private Integer age; private String email; }
关于type属性(主键策略)的枚举值IdType的详细介绍:
常用的有:
@TableId(value='对应的表主键列名' , type=idType.AUTO) @TableId(value='对应的表主键列名' , type=idType.ASSIGN_ID)
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert 前自行 set 主键值 |
ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认 default 方法) |
ID_WORKER | 分布式全局唯一 ID 长整型类型(please use ASSIGN_ID ) |
UUID | 32 位 UUID 字符串(please use ASSIGN_UUID ) |
ID_WORKER_STR | 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID ) |
关于主键生成策略,可以在SpringBoot的全局配置文件中进行配置
# 配置日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 全局配置 global-config: db-config: # 配置表的名的前缀 table-prefix: tb_ # 配置主键生成策略 id-type: auto
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。
雪花算法的原理就是生成一个的 64 位比特位的 long 类型的唯一 id。
对于每一个雪花算法服务,需要先指定 10 位的机器码,这个根据自身业务进行设定即可。例如机房号+机器号,机器号+服务号,或者是其他可区别标识的 10 位比特位的整数值都行。
简单说雪花算法解决的问题:
不同的编程语言,都有雪花算法的实现,可以直接调用对应的程序即可得到一个雪花算法值。
作用:字段注解(非主键),解决表的属性名与实体中的属性名不一致问题。
@Data
@TableName("user")
public class User {
@TableId
private Long id;
@TableField("nickname")
private String name;
private Integer age;
private String email;
}
@TableField注解的属性比较多,常用的就是标注列名(即value属性,默认可以不写)
其他的属性如果需要,可以参考官方文档
作用:表字段逻辑处理注解(逻辑删除)
物理删除:真实删除,将对应的数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应的数据中代表是否删除的字段状态修改
被删除
,在数据库可以查到这条数据记录,只是标记为被删除
使用场景:可以进行数据恢复
@Data
@TableName("user")
public class User {
@TableId
private Long id;
@TableField("nickname")
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDeleted;
}
注意:
isDeleted
属性来标准逻辑删除,就需要在表中添加这么一列,可以将未删除的状态设置为0,删除就对应的为1, where is_deleted = 0
表示只查询未被删除的,is_deleted
的值设置为1在使用mybatis-plus的时候,发现修改、删除、查询的都对应有Wrapper,他就是用于构建各种条件。是构造条件的顶级父类。
@Test
public void testQueryWrapper(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询姓名中包含字母o,邮箱不为null,年龄大于等于20的
wrapper.like("name","o")
.isNotNull("email")
.ge("age",20);
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:SELECT id,name,age,email FROM user WHERE (name LIKE ? AND email IS NOT NULL AND age >= ?)
@Test
public void testQueryWrapper2(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询年龄在20到30之间的,先按照年龄降序,年龄相同在按照姓名升序
wrapper.between("age",20,30)
.orderByDesc("age")
.orderByAsc("name");
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:SELECT id,name,age,email FROM user WHERE (age BETWEEN ? AND ?) ORDER BY age DESC,name ASC
@Test
public void testQueryWrapper3(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 删除邮箱为空的数据
wrapper.isNull("email");
int i = this.userMapper.delete(wrapper);
System.out.println(i);
}
最终生成的sql:DELETE FROM user WHERE (email IS NULL)
@Test
public void testQueryWrapper4(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 修改name中包含字母a的数据
wrapper.like("name","a");
User user = new User();
user.setAge(26);
user.setEmail("[email protected]");
int i = this.userMapper.update(user, wrapper);
System.out.println(i);
}
最终生成的sql:UPDATE user SET age=?, email=? WHERE (name LIKE ?)
@Test
public void testQueryWrapper5(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询name、age两列数据
wrapper.select("name","age");
List<Map<String, Object>> maps = this.userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
最终生成的sql:SELECT name,age FROM user
默认情况下,使用QueryWrapper进行条件组装的时候,多个条件之间使用的and进行连接,QueryWrapper中提供and和or方法,可以提供指定的查询条件的优先级。
@Test
public void testQueryWrapper6(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询年龄在20到30之间并且(姓名包含字母a或者邮箱不为null的数据)
wrapper.between("age",20,30)
.and(i->i.like("name","a").or().isNotNull("email"));
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:
SELECT id,name,age,email FROM user WHERE (age BETWEEN ? AND ? AND (name LIKE ? OR email IS NOT NULL))
@Test
public void testQueryWrapper7(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询年龄在20到30之间的所有数据,这里故意采用子查询的方式完成
wrapper.inSql("age","select age from user where age >= 20 and age <= 30");
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:
SELECT id,name,age,email FROM user WHERE (age IN (select age from user where age >= 20 and age <= 30))
虽然QueryWrapper可以完成修改操作的,但是需要传递实体对象,还是有点小麻烦。提供的UpdateWrapper是专门用于完成修改条件和数据封装
@Test
public void testUpdateWrapper(){
// 创建UpdateWrapper对象,用于构建查询条件
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
// 修改name中包含字母a的数据
// 设置修改的的条件
wrapper.like("name","a");
// 设置需要修改的数据
wrapper.set("age",22).set("email","[email protected]");
int i = this.userMapper.update(null, wrapper);
System.out.println(i);
}
最终生成的sql:
UPDATE user SET age=?,email=? WHERE (name LIKE ?)
condition用于在进行条件封装的时候,判断某个值,当这个值不为null的时候,会自动添加对应的条件,如果为null,就不会添加条件
简单说:condition可以动态根据条件组装条件
@Test
public void testQueryWrapper9(){
// 创建QueryWrapper对象,用于构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 根据指定的数据,添加查询条件,模拟多条件查询的场景
String name = "a";
Integer age = 20;
String email = null;
wrapper.like(StringUtils.isNotBlank(name),"name",name)
.gt(age!=null , "age",age)
.eq(StringUtils.isNotBlank(email),"email",email);
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:生成的sql中并不包含email,因为email为null
SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age > ?)
Lambda相关的查询与修改构造器,针对QueryWrapper和UpdateWrapper在构造添加的时候,需要书写表的列名进行优化,通过Lambda相关的条件构造器,可以将对应的条件采用实体类的实型进行编写。
@Test
public void testQueryWrapper10(){
// 创建QueryWrapper对象,用于构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 查询姓名中包含字母o,邮箱不为null,年龄大于等于20的
wrapper.like(User::getName,"o")
.isNotNull(User::getEmail)
.ge(User::getAge,20);
List<User> users = this.userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
最终生成的sql:生成的sql中并不包含email,因为email为null
SELECT id,name,age,email FROM user WHERE (name LIKE ? AND email IS NOT NULL AND age >= ?)
@Test
public void testUpdateWrapper(){
// 创建UpdateWrapper对象,用于构建查询条件
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
// 修改name中包含字母a的数据
// 设置修改的的条件
wrapper.like(User::getName,"a");
// 设置需要修改的数据
wrapper.set(User::getAge,22).set(User::getEmail,"[email protected]");
int i = this.userMapper.update(null, wrapper);
System.out.println(i);
}
最终生成的sql:生成的sql中并不包含email,因为email为null
UPDATE user SET age=?,email=? WHERE (name LIKE ?)
MyBatis-Plus的分页非常简单,只需要添加一个配置类即可,而MyBatis-Plus提供的配置是以插件的方式提供。
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
// 创建拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 设置分页的拦截器,并设置数据库类型为mysql
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 返回拦截器对象
return interceptor;
}
}
通过上面代码的配置,就可以使用分页功能
@Test
public void testPagination(){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge,20);
Page<User> page = new Page<>(1,3);
// 如果没有条件,第二个参数可以设置为null
this.userMapper.selectPage(page , wrapper);
// 查询的结果就放在Page对象中
// 获取当前分页总页数
System.out.println(page.getPages());
// 每页显示条数,默认 10
System.out.println(page.getSize());
// 当前页
System.out.println(page.getCurrent());
// 当前满足条件的总数
System.out.println(page.getTotal());
// 分页查询的数据
System.out.println(page.getRecords());
}
在实际使用中,难免会出现需要自己书写sql语句,同时还需要使用分页功能,这时就需要自定义分页功能
需要注意:
public interface UserMapper extends BaseMapper<User> {
@Select("select * from user where age > #{age}")
public Page<User> selectUserAndPage(@Param("page") Page<User> page , @Param("age") Integer age);
}
@Test
public void testPagination2(){
Page<User> page = new Page<>(1,3);
// 如果没有条件,第二个参数可以设置为null
this.userMapper.selectUserAndPage(page , 20);
// 查询的结果就放在Page对象中
// 获取当前分页总页数
System.out.println(page.getPages());
// 每页显示条数,默认 10
System.out.println(page.getSize());
// 当前页
System.out.println(page.getCurrent());
// 当前满足条件的总数
System.out.println(page.getTotal());
// 分页查询的数据
System.out.println(page.getRecords());
}
MySQL中的乐观锁和悲观锁主要区别如下:
InnoDB存储引擎从MySQL 5.5开始支持行锁,可以实现行级别的悲观锁。
总体来说,乐观锁适用于写比较少的场景,可以提高吞吐量。悲观锁适用于写比较多的场景,可以保证数据的完整性。
为了测试,重新创建一张表
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(100),
price DOUBLE ,
num INT,
VERSION INT DEFAULT 0
);
INSERT INTO goods(id,NAME,price,num,VERSION) VALUES(NULL,"华为手机",100,3,0);
需要在数据库表中添加一列version,用于乐观锁的版本号确认,需要在实体类中使用@Version注解。
@Data
@TableName("goods")
public class Goods {
@TableId
private Long id;
private String name;
private Integer price;
private Integer num;
@Version
private Integer version;
}
需要在拦截器中添加乐观锁的插件
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
// 创建拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 设置分页的拦截器,并设置数据库类型为mysql
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁的拦截器
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 返回拦截器对象
return interceptor;
}
}
@SpringBootTest
public class GoodsTest {
@Autowired
private GoodsMapper goodsMapper;
@Test
public void test(){
// 架设一个操作:需要对商品的价格先增加50%,然后在优惠20%
// 架设另一个操作:需要对商品的价格在前一个修改后的基础上在优惠10%
// 但是可能会出现两个操作同时进行的情况,就需要通过乐观锁进行控制
// 架设两个操作同时进行,操作之前需要先查询到当前商品的数据信息
Goods goods = this.goodsMapper.selectById(1);
Goods goods2 = this.goodsMapper.selectById(1);
// 开始执行 价格先增加50%
goods.setPrice(goods.getPrice()*1.5);
// 然后在优惠20%
goods.setPrice(goods.getPrice()*0.8);
// 将数据跟新到数据库
int i = this.goodsMapper.updateById(goods);
System.out.println(i);
// goods2进行更新
goods2.setPrice(goods2.getPrice()*0.9);
// goods2更新不会成功
this.goodsMapper.updateById(goods2);
// 若要更新成功,就必须在更新失败后,重新获取数据,对最新的数据进行更新
}
}
通过执行的过程中产生的sql语句,会发现,version作为where的条件存在
UPDATE goods SET name=?, price=?, num=?, version=? WHERE id=? AND version=?
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx
搜索并安装。安装成功会提示重启idea,我们重庆idea之后,就可以使用MybatisX插件了
使用前提:需要配置数据库源
选择 +
按钮,然后选择Data Source
菜单,找到 MySql
选项
在弹出窗口中,填写对应的信息,最后点击OK
,即可完成数据源的配置
打开数据源,找到对应的表,右击选择MyBatisX-Generator
需要根据提示,填写对应的内容
继续完成对应的配置
最后选择Finish
,即可生成最基础的模版,然后就可以开始愉快的编程了。
在mybatis-plus默认的功能不够的时候,就可以借助MyBaitsX快速给Mapper接口添加对应的方法,同时也会在mapper文件中生成对应的sql 语句。