一、简述
简称MP,是在Mybatis的基础上进行增强,用户简化开发,提高效率。只做增强不做改变
支持
主键自动生成
、内置代码生成器
、内置分页插件
官网:MyBatis-Plus
二、快速开始
2.1 设计表
1. SET NAMES utf8mb4; 2. SET FOREIGN_KEY_CHECKS = 0; 3. 4. -- ---------------------------- 5. -- Table structure for t_user 6. -- ---------------------------- 7. DROP TABLE IF EXISTS `t_user`; 8. CREATE TABLE `t_user` ( 9. `id` int(11) NOT NULL AUTO_INCREMENT, 10. `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 11. `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 12. `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 13. `age` int(11) NULL DEFAULT NULL, 14. `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 15. PRIMARY KEY (`id`) USING BTREE 16. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 17. 18. -- ---------------------------- 19. -- Records of t_user 20. -- ---------------------------- 21. INSERT INTO `t_user` VALUES (1, 'zs', '1234', '张三', 14, '北京'); 22. INSERT INTO `t_user` VALUES (2, 'ls', '1234', '李四', 15, '河南'); 23. 24. SET FOREIGN_KEY_CHECKS = 1; 25.
2.2 添加依赖
1. <dependency> 2. <groupId>com.baomidou</groupId> 3. <artifactId>mybatis-plus-boot-starter</artifactId> 4. <version>3.5.1</version> 5. </dependency>
2.3 配置数据源
1. spring: 2. datasource: 3. driver-class-name: com.mysql.jdbc.Driver 4. url: jdbc:mysql://localhost:3306/xxx 5. username: root 6. password: 1234
2.4 创建POJO
1. @Data 2. @AllArgsConstructor 3. @NoArgsConstructor 4. @TableName("t_user") 5. public class User { 6. private Integer id; 7. private String userName; 8. private String password; 9. private String nickName; 10. private Integer age; 11. private String address; 12. }
2.5 创建Mapper接口
创建的Mapper接口去继承
BaseMapper
,泛型为POJO的类型
1. public interface UserMapper extends BaseMapper<User> { 2. }
2.6 创建启动类
在启动类上添加
@MapperScan
注解,扫描Mapper接口所在的包
1. @SpringBootApplication 2. @MapperScan("com.jiuxiao.mapper") 3. public class MPApp { 4. public static void main(String[] args) { 5. SpringApplication.run(MPApp.class,args); 6. } 7. }
2.7 编写测试方法
1. @SpringBootTest 2. public class MPTest { 3. 4. @Autowired 5. UserMapper userMapper; 6. 7. @Test 8. void testSelectAll() { 9. List<User> users = userMapper.selectList(null); 10. System.out.println(users); 11. } 12. }
三、常用设置
3.1 日志打印
在控制台中打印操作的SQL语句
1. mybatis-plus: 2. configuration: 3. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.2 设置表映射规则
默认情况下MP在操作表名时,操作的是类名,如果类名和表名不一致就需要进行手动设置映射规则
① 单个设置
在实体类上添加
@TableName
注解,声明数据库表名
1. @TableName("t_user") 2. public class User { 3. // ... 4. }
② 全局设置
如果每个表的前缀都是
t_
,需要在每一个实体类上添加注解,比较频繁。可以使用以下配置
1. mybatis-plus: 2. global-config: 3. db-config: 4. table-prefix: t_
3.3 开启字段和属性的驼峰映射
从经典数据库列名A_COLUMN映射到经典Java属性名aColumn。
举例:
- 字段名为
user_name
,自动映射成userName
使用前提:
- SQL中的字段名:全部小写,每个单词之间使用下划线分割
- java中的属性名:首字母小写,其余单词首字母大写
该配置默认是开启的,无需任何配置
1. mybatis-plus: 2. configuration: 3. map-underscore-to-camel-case: true
3.4 字段和属性映射
MP中默认操作的字段名为实体类中的属性名,如果字段名和属性名不一致,需要手动映射
在实体类的属性上添加
@TableField
注解,声明表中字段名
1. public class User { 2. @TableField("user_name") 3. private String userName; 4. 5. @TableField("nick_name") 6. private String nickName; 7. }
3.5 主键生成策略
- AUTO
使用数据库中的自增策略,前提是表中主键声明了自增
- NONE
默认策略(没有设置主键类型),使用雪花算法生成一个10位的数字
- INPUT
手动给主键赋值,否则表中字段值为
null
- ASSING_ID
当实体类中的主键属性值为
null
时,使用雪花算法分配ID【当前主键类型必须是Long和Integer和String】
- ASSING_UUID
当实体类中的主键属性值为
null
时,使用UUID分配ID【当前主键类型必须是String】① 单个设置
在属性上添加
@TableId
注解,声明是一个主键
- value:对应的是表中的主键字段名,如果字段名为
id
,该属性可以省略- type:是一个枚举类型,声明主键生成策略
1. public class User { 2. @TableId(value = "id",type = IdType.xxx) 3. private Integer id; 4. }
② 全局设置
如果每个表中的主键生成策略一致,可以使用全局配置
1. mybatis-plus: 2. global-config: 3. db-config: 4. id-type: auto
四、CRUD
4.1 增加
- 传入实体类对象,增加之后通过
getter
方法可以获取主键值
1. @Test 2. void testInsert() { 3. User user = new User(null, "ww", "4444", "王五", 16, "河北"); 4. userMapper.insert(user); 5. System.out.println("增加之后的主键 --> "+user.getId()); 6. }
4.2 查询
- 根据
id
查询
1. @Test 2. void testSelectById() { 3. User user = userMapper.selectById(1); 4. System.out.println(user); 5. }
- 多条件查询
1. @Test 2. void testSelectByMap() { 3. Map<String,Object> map = new HashMap<>(); 4. map.put("age",14); 5. map.put("address","北京"); 6. List<User> list = userMapper.selectByMap(map); 7. System.out.println(list); 8. }
- 批量查询
1. @Test 2. void testSelectBatch() { 3. List<Integer> ids = Stream.of(2, 3).collect(Collectors.toList()); 4. List<User> users = userMapper.selectBatchIds(ids); 5. System.out.println(users); 6. }
4.3 更新
- 只更属性值不为
null
的字段,如果类型为基本数据类型,相对应的字段将会被修改成默认值比如
int
默认值为0,对应的字段值将会被修改成0
1. @Test 2. void testUpdate() { 3. User user = new User(); 4. user.setId(1); 5. user.setAddress("河北"); 6. user.setAge(66); 7. userMapper.updateById(user); 8. }
4.4 删除
- 根据
id
删除
1. @Test 2. void testDeleteById() { 3. userMapper.deleteById(3); 4. }
- 多条件删除
也可以传入对象
1. @Test 2. void testDeleteByMap() { 3. Map<String,Object> map = new HashMap<>(); 4. map.put("nick_name","李四"); 5. map.put("user_name","ls"); 6. userMapper.deleteByMap(map); 7. }
- 批量删除
1. @Test 2. void testDeleteBatch() { 3. List<Integer> ids = Stream.of(1, 2).collect(Collectors.toList()); 4. userMapper.deleteBatchIds(ids); 5. }
五、条件构造器Wrapper
5.1 概述
在操作数据库中会牵扯到很多条件,所以MP提供了一个强大的条件构造器
Wrapper
,使用它可以让我们非常方便的构造条件继承体系:
在其子类
AbstractWrapper
中提供了很多构建Where条件的方法
AbstractWrapper
的子类QueryWrapper
中提供了用于针对SELECT语句的select
方法,可以设置只查询哪些字段
AbstractWrapper
的子类UpdateWrapper
中提供了用户针对SET语句的set
方法,可以设置只更新哪些字段5.2 AbstractWrapper中常用方法
eq
- 等于 =
- 例:
eq(name,"张三")
-->name = '张三'
allEq
1. allEq(Map<R, V> params) 2. allEq(Map<R, V> params, boolean null2IsNull) 3. allEq(boolean condition,BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
参数说明
- params:
key
为数据库中的字段名,value
是条件- null2IsNull:为
true
时,map
集合中value
为null
的调用isNull方法;为false
时则忽略value
为null
的- filter:过滤函数,是否允许字段传入比对条件中
例
allEq({id : 1,name : "张三",age : null})
-->id = 1 and name = '张三' and age is null
allEq({id : 1,name : "张三",age : null},false)
-->id = 1 and name = '张三'
allEq((k,v)->k.indexOf('a') >= 0,{id : 1,name : "张三",age : null},false)
-->name = '张三'
ne
- 不等于 <>
- 例:
ne("name","李四")
-->name <> '李四'
gt
- 大于 >
- 例:
gt("age",18)
-->age > 18
ge
- 大于等于 >=
- 例:
ge("age",18)
-->age >= 18
lt
- 小于 <
- 例:
lt("age",18)
-->age < 18
le
- 小于等于 <=
- 例:
le("age",18)
-->age <= 18
between
- BETWEEN 值1 AND 值2
- 例:
between("age",15,40)
-->age BETWEEN 15 AND 40
like
- LIKE %值%
- 例:
like("name","王")
-->name like '%王%'
likeLeft
- LIKE %值
- 例:
likeLeft("name","三")
-->name like '%三'
likeRight
- LIKE 值%
- 例:
likeRight("name","李")
-->name like '李%'
isNull
- 字段 IS NULL
- 例:
inNull("address")
-->address is null
isNotNull
- 字段 IS NOT NULL
- 例:
isNotNull("id")
-->id is not null
in
- 字段 IN (v0,v1)
- 例:
in("id",1,2,3)
-->id in (1, 2, 3)
① 示例
需求:查询address以"河"开头、成年的用户信息
SQL语句
1. select 2. * 3. from 4. t_user 5. where 6. address like '河%' 7. and 8. age >= 18
使用Wrapper查询
1. @Test 2. void testWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(); 4. wrapper.likeRight("address","河"); 5. wrapper.ge("age",18); 6. List<User> users = userMapper.selectList(wrapper); 7. System.out.println(users); 8. }
② 示例
需求:查询id为1,2,3、age在20和25之间、password为1234的用户信息
SQL语句
1. select 2. * 3. from 4. t_user 5. where 6. id in (1,2,3) 7. and 8. age between 20 and 25 9. and 10. `password` = '1234'
使用Wrapper查询
1. @Test 2. void testWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(); 4. wrapper.in("id",1,2,3); 5. wrapper.between("age",20,25); 6. wrapper.eq("password","1234"); 7. List<User> users = userMapper.selectList(wrapper); 8. System.out.println(users); 9. }
③ 示例
需求:查询address中含有北,nick_name为不为空的用户信息,并且根据id倒序
SQL语句
1. select 2. * 3. from 4. t_user 5. where 6. `address` like '%北%' 7. and 8. nick_name is not null 9. order by 10. id desc
使用Wrapper查询
1. @Test 2. void testWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(); 4. wrapper.like("address","北"); 5. wrapper.isNotNull("nick_name"); 6. wrapper.orderByDesc("id"); 7. List<User> users = userMapper.selectList(wrapper); 8. System.out.println(users); 9. }
5.3 QueryWrapper
使用
select
方法可以设置要查询的字段① 方法一
select(String... sqlSelect);
参数
- 查询的字段
1. @Test 2. void testQueryWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(); 4. wrapper.select("id","nick_name","address"); 5. List<User> users = userMapper.selectList(wrapper); 6. System.out.println(users); 7. }
② 方法二
select(Predicate<TableFieldInfo> predicate);
参数
- 需要过滤的字段【无法过滤id】
1. @Test 2. void testQueryWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(new User()); 4. wrapper.select(tableFieldInfo -> "nick_name".equals(tableFieldInfo.getColumn()) || "address".equals(tableFieldInfo.getColumn())); 5. List<User> users = userMapper.selectList(wrapper); 6. System.out.println(users); 7. }
③ 方法三
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate);
参数
- 实体类字节码
- 需要过滤的字段【无法过滤id】
1. @Test 2. void testQueryWrapper() { 3. QueryWrapper<User> wrapper = new QueryWrapper<>(); 4. wrapper.select(User.class,tableFieldInfo -> !"user_name".equals(tableFieldInfo.getColumn())); 5. List<User> users = userMapper.selectList(wrapper); 6. System.out.println(users); 7. }
5.4 UpdateWrapper
使用
set
方法设置只修改哪些字段,在使用的过程中还能够去构建复杂的WHERE条件
1. @Test 2. void testUpdateWrapper() { 3. UpdateWrapper<User> wrapper = new UpdateWrapper<>(); 4. wrapper.gt("age",18); 5. wrapper.set("password","6666"); 6. userMapper.update(new User(),wrapper); 7. }
5.5 LambdaWrapper
之前在构造条件时字段名都是以字符串的形式,这种无法在预编译期确定字段名的合法性
MP提供了Lambda条件构造器,直接以实体类的方法引用形式来指定字段名
1. @Test 2. void testLambdaWrapper() { 3. LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); 4. wrapper.likeRight(User::getAddress,"河"); 5. wrapper.ge(User::getAge,"18"); 6. wrapper.select(User::getUserName,User::getAddress); 7. List<User> users = userMapper.selectList(wrapper); 8. System.out.println(users); 9. }
六、Mapper层自定义方法
6.1 概述
虽然MP提供了很多常用的方法,但遇到一些复杂的语句时,就需要手动编写SQL语句
因为MP是在MyBatis的基础上增强,所以还是支持之前MyBatis的方式自定义方法
同时还支持在自定义方法中能够继续使用条件构造器Wrapper
6.2 Mybatis方式
① 定义接口方法
1. public interface UserMapper extends BaseMapper<User> { 2. 3. User selectById(Integer id); 4. }
② 定义xml文件
1. <?xml version="1.0" encoding="UTF-8" ?> 2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 3. <mapper namespace="com.jiuxiao.mapper.UserMapper"> 4. 5. </mapper>
③ 编写SQL语句
1. <sql id="Bean_column"> 2. id,user_name,password,nick_name,age,address 3. </sql> 4. <select id="selectById" resultType="com.jiuxiao.pojo.User"> 5. select 6. <include refid="Bean_column"/> 7. from 8. t_user 9. where 10. id = #{id} 11. </select>
6.3 使用条件构造器
在使用自定义方法时,如果也想像MP中自定的方法一样使用条件构造器,只需要以下配置:
- 方法定义中将Wrapper以参数传入,并指定其参数名
1. public interface UserMapper extends BaseMapper<User> { 2. 3. List<User> selectByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper); 4. }
- 在SQL语句中将Wrapper中的SQL片段进行拼接
注意:不能使用
#{}
,必须使用${}
,因为该SQL片段中含有关键字,需要经过预编译,不能以占位符的形式传递
1. <select id="selectByWrapper" resultType="com.jiuxiao.pojo.User"> 2. select 3. <include refid="Bean_column"/> 4. from 5. t_user 6. ${ew.customSqlSegment} 7. </select>
七、分页查询
7.1 单表分页查询
- 配置分页查询拦截器
1. @Configuration 2. public class MybatisPlusConfig { 3. @Bean 4. public MybatisPlusInterceptor MybatisPlusInterceptor() { 5. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 6. interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 7. return interceptor; 8. } 9. }
- 测试分页查询
1. @Test 2. void testPage() { 3. IPage<User> page = new Page<>(); 4. // 设置每页条数 5. page.setSize(3); 6. // 设置查询页数 7. page.setCurrent(1); 8. page = userMapper.selectPage(page,null); 9. System.out.println(page.getRecords()); // 获取每页数据 10. System.out.println(page.getTotal()); // 获取总条数 11. }
7.2 多表分页查询
如果在进行多表查询时,并使用到分页,就可以在Mapper接口中自定义方法,然后让方法接收Page对象
需求:查询所有的用户信息以及查询所属订单
- 表设计
1. SET NAMES utf8mb4; 2. SET FOREIGN_KEY_CHECKS = 0; 3. 4. -- ---------------------------- 5. -- Table structure for t_order 6. -- ---------------------------- 7. DROP TABLE IF EXISTS `t_order`; 8. CREATE TABLE `t_order` ( 9. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', 10. `price` int(11) NULL DEFAULT NULL COMMENT '价格', 11. `user_id` int(11) NULL DEFAULT NULL COMMENT '创建人id', 12. `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', 13. `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', 14. `version` int(11) NULL DEFAULT 1 COMMENT '版本号', 15. `del_flag` int(1) NULL DEFAULT 1 COMMENT '1-已删除 0-未删除', 16. PRIMARY KEY (`id`) USING BTREE 17. ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 18. 19. -- ---------------------------- 20. -- Records of t_order 21. -- ---------------------------- 22. INSERT INTO `t_order` VALUES (1, 200, 1, '2022-10-19 21:28:52', '2022-10-22 21:28:54', 1, 0); 23. INSERT INTO `t_order` VALUES (2, 100, 1, '2022-10-08 21:29:11', '2022-10-18 21:29:15', 1, 0); 24. INSERT INTO `t_order` VALUES (3, 15, 2, '2022-09-29 21:29:39', '2022-10-15 21:29:43', 1, 0); 25. 26. SET FOREIGN_KEY_CHECKS = 1;
- 定义实体类
1. @Data 2. @AllArgsConstructor 3. @NoArgsConstructor 4. public class Order { 5. private Long id; 6. private Integer price; 7. private Integer userId; 8. private Date createTime; 9. private Date updateTime; 10. private Integer version; 11. private Integer delFlag; 12. }
- 定义Mapper接口
只需要定义,无需处理,拦截器会自动处理
1. public interface UserMapper extends BaseMapper<User> { 2. 3. IPage<User> selectByPage(Page<User> page); 4. }
- 编写SQL语句:
1. <resultMap id="Bean_result" type="com.jiuxiao.pojo.User"> 2. <id property="id" column="id"/> 3. <result property="userName" column="user_name"/> 4. <result property="password" column="password"/> 5. <result property="nickName" column="nick_name"/> 6. <result property="age" column="age"/> 7. <result property="address" column="address"/> 8. <collection property="list" 9. select="com.jiuxiao.mapper.OrderMapper.selectByUserId" 10. fetchType="lazy" 11. column="id"/> 12. </resultMap> 13. <select id="selectByPage" resultMap="Bean_result"> 14. select 15. id,user_name 16. from 17. t_user 18. </select>
- 分步查询OrderMapper接口
1. public interface OrderMapper{ 2. 3. @Select("select * from t_order where user_id = #{id}") 4. List<Order> selectByUserId(Integer id); 5. }
- 测试
1. @Test 2. void testPageMethod() { 3. IPage<User> page = new Page<>(); 4. page.setSize(2); 5. page.setCurrent(1); 6. page = userMapper.selectByPage(page); 7. System.out.println(page.getRecords()); 8. System.out.println(page.getTotal()); 9. }
八、Service接口
8.1 概述
在实际业务中肯定需要定义Service接口,MP中提供了Service层的实现。接口继承
IService
,其实现类继承ServiceImpl
,即可使用相对于Mapper接口,Service层提供了更多批量操作的方法
8.2 使用
- 接口
1. public interface UserService extends IService<User> { 2. 3. }
- 实现类
1. @Service 2. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { 3. 4. }
- 测试
1. @Autowired 2. UserService userService; 3. 4. @Test 5. void testServiceMethod() { 6. User user = userService.getById(3); 7. System.out.println(user); 8. }
其他方法查看官方文档
8.3 自定义Service层方法
- 接口
1. public interface UserService extends IService<User> { 2. 3. User selectByIdOfOrder(Integer id); 4. }
- 实现类
1. @Service 2. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { 3. 4. @Autowired 5. OrderMapper orderMapper; 6. 7. @Override 8. public User selectByIdOfOrder(Integer id) { 9. // 获取mapper 10. UserMapper userMapper = baseMapper; 11. User user = userMapper.selectById(1); 12. List<Order> orders = orderMapper.selectByUserId(user.getId()); 13. user.setList(orders); 14. return user; 15. } 16. }
- 测试
1. @Autowired 2. UserService userService; 3. 4. @Test 5. void testCustomService() { 6. User user = userService.selectByIdOfOrder(1); 7. System.out.println(user); 8. }
九、自动填充
9.1 概述
在实际业务中的表都会有更新时间、创建时间等字段
可以使用
@TableField
注解中的fill
属性来设置字段的自动填充9.2 使用
① 添加注解
使用
@TableField
注解中的fill
属性来标注哪些字段需要自动填充,加了注解MP才会在SQL语句中为我们预留字段。该属性的属性值就代表进行什么操作时进行自动填充,该属性值有:
- INSERT:进行增加操作时自动填充
- UPDATE:进行更新操作时自动填充
- INSERT_UPDATE:进行增加和更新时自动填充
- DEFAULT:默认不处理
1. public class Order { 2. // ... 3. @TableField(fill = FieldFill.INSERT) 4. private Date createTime; 5. @TableField(fill = FieldFill.UPDATE) 6. private Date updateTime; 7. }
② 自定义填充处理器 MyMetaObjectHandler
1. @Component 2. public class MyMetaObjectHandler implements MetaObjectHandler { 3. 4. @Override 5. public void insertFill(MetaObject metaObject) { 6. // this.setFieldValByName("createTime",LocalDateTime.now(),metaObject); 7. this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); 8. } 9. 10. @Override 11. public void updateFill(MetaObject metaObject) { 12. // this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject); 13. this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); 14. } 15. }
注意:
- 未注释的地方,时间类型只能是
LocalDateTime
- 注释的地方,可以传入任意类型。比如:
Date
,LocalDateTime
- 如果参数中的类型和实体类中的类型不匹配,传入的值为
null
- 自动填充的值为
LocalDateTime.now()
,那么对应的属性类型必须为LocalDateTime
- 自动填充的值为
new Date()
,那么对应的属性类型必须为Date
- 如果
mysql-connector-java
版本低于5.1.37,使用Java8
的日期类型会抛出异常
- 异常信息:Data truncation: Incorrect datetime value: '\xAC\xED\x00\x05sr\x00\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C\x00\x00xpw\x0E\x05\x00\x00\x07\xE6\x0A\x14\x0C\x0B2$\xA8'\xC0' for column 'update_time' at row 1
十、逻辑删除
MP也支持逻辑删除的处理,只需要配置好逻辑删除的的实体类属性名,再指定删除的值和未删除的值即可
注意:如果是3.3.0之前的版本,属性类中需要加入
@TableLogic
注解
1. mybatis-plus: 2. global-config: 3. db-config: 4. logic-delete-field: delFlag # 实体类属性名 5. logic-delete-value: 1 # 逻辑已删除值(默认是1) 6. logic-not-delete-value: 0 # 逻辑未删除值(默认是0)
- 删除
将逻辑删除的字段修改成
UPDATE t_order SET del_flag=1 WHERE id=? AND del_flag=0
- 查询
SELECT id,price,user_id,create_time,update_time,version,del_flag FROM t_order WHERE del_flag=0
十一、乐观锁
11.1 概述
在并发操作时,我们需要保证对数据进行操作时不会发生冲突。乐观锁就是一种解决方案,通过版本号防止操作数据发生冲突问题
使用乐观锁,一般会在表中加入一个version字段,每次对某个列进行操作,对应的version版本+1
在进行更新时操作时,先去查询对应数据的version值,然后在执行修改的时候:set version = 老版本 + 1 where version = 老版本
如果在此期间有人修改了这个版本号,那么这次的修改将会失败
手动实现乐观锁有点麻烦,所以MP为我们提供了插件进行使用
11.2 演示冲突
两个线程
同时
去更新同一条
数据
1. @Test 2. void testVersion() { 3. new Thread(() -> { 4. Order order = new Order(); 5. order.setId(6L); 6. order.setPrice(55); 7. int count = orderMapper.updateById(order); 8. System.out.println("分支线程 -->"+count); 9. }).start(); 10. Order order = new Order(); 11. order.setId(6L); 12. order.setPrice(66); 13. int count = orderMapper.updateById(order); 14. System.out.println("主线程 -->"+count); 15. }
都完成了修改:最后一次修改后的数据覆盖第一次修改后的数据
1. ==> Preparing: UPDATE t_order SET price=?,WHERE id=? 2. ==> Preparing: UPDATE t_order SET price=?, update_time=? WHERE id=? 3. ==> Parameters: 55(Integer), 6(Long) 4. ==> Parameters: 66(Integer), 6(Long) 5. <== Updates: 1 6. <== Updates: 1 7. 分支线程 -->1 8. 主线程 -->1
11.3 使用乐观锁解决
① 手动解决
1. @Test 2. void testVersion() { 3. new Thread(() -> { 4. Order order = orderMapper.selectById(6L); 5. UpdateWrapper<Order> wrapper = new UpdateWrapper<>(); 6. wrapper.set("price",333); 7. wrapper.set("version",order.getVersion()+1); 8. wrapper.eq("id",order.getId()); 9. wrapper.eq("version",order.getVersion()); 10. int count = orderMapper.update(new Order(), wrapper); 11. System.out.println("分支线程 -->"+count); 12. }).start(); 13. Order order = orderMapper.selectById(6L); 14. UpdateWrapper<Order> wrapper = new UpdateWrapper<>(); 15. wrapper.set("price",222); 16. wrapper.set("version",order.getVersion()+1); 17. wrapper.eq("id",order.getId()); 18. wrapper.eq("version",order.getVersion()); 19. int count = orderMapper.update(new Order(), wrapper); 20. System.out.println("主线程 -->"+count); 21. }
1. ==> Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?) 2. ==> Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?) 3. ==> Parameters: 222(Integer), 2(Integer), 6(Long), 1(Integer) 4. ==> Parameters: 333(Integer), 2(Integer), 6(Long), 1(Integer) 5. <== Updates: 0 6. 分支线程 -->0 7. <== Updates: 1 8. 主线程 -->1
② 借助MP
- 在属性上加上
@Version
注解
1. public class Order { 2. // ... 3. @Version 4. private Integer version; 5. }
- 配置对应的插件
1. @Configuration 2. public class MybatisPlusConfig { 3. @Bean 4. public MybatisPlusInterceptor versionMybatisPlusInterceptor() { 5. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 6. interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); 7. return interceptor; 8. } 9. }
- 测试
1. @Test 2. void testVersion() { 3. new Thread(() -> { 4. Order order = orderMapper.selectById(6L); 5. order.setPrice(1234); 6. int count = orderMapper.updateById(order); 7. System.out.println("分支线程 -->"+count); 8. }).start(); 9. Order order = orderMapper.selectById(6L); 10. order.setPrice(6789); 11. int count = orderMapper.updateById(order); 12. System.out.println("主线程 -->"+count); 13. }
1. ==> Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=? 2. ==> Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=? 3. ==> Parameters: 1234(Integer), 2(Integer), 6(Long), 1(Integer) 4. ==> Parameters: 6789(Integer), 2(Integer), 6(Long), 1(Integer) 5. <== Updates: 0 6. <== Updates: 1
11.4 多插件问题
在MP3.4.0版本以后,如果需要配置多个插件的时候要注意:
- 只需要注入一个
MybatisPlusInterceptor
对象,然后将插件对象进行添加- 需要注意顺序关系,建议使用一下顺序:
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除
例如:
1. @Configuration 2. public class MybatisPlusConfig { 3. 4. @Bean 5. public MybatisPlusInterceptor MybatisPlusInterceptor() { 6. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 7. interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 分页插件 8. interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件 9. return interceptor; 10. } 11. }
十二、代码生成器
MP提供的代码生成器按照要求能够自动生成Service层、Controller层、实体类、Mapper层。
1. <dependency> 2. <groupId>com.baomidou</groupId> 3. <artifactId>mybatis-plus-generator</artifactId> 4. <version>3.5.1</version> 5. </dependency> 6. <dependency> 7. <groupId>org.freemarker</groupId> 8. <artifactId>freemarker</artifactId> 9. </dependency>
1. public class MybatisPlusGenerate { 2. // 项目全路径 3. public static final String FILE_URL = System.getProperty("user.dir"); 4. public static final String URL = "jdbc:mysql://localhost:3306/jiuxiao"; 5. public static final String USER_NAME = "root"; 6. public static final String PASSWORD = "1234"; 7. public static void main(String[] args) { 8. FastAutoGenerator.create(URL,USER_NAME,PASSWORD) 9. .globalConfig(builder -> { 10. builder.author("酒萧") // 设置作者 11. .enableSwagger() // 开启 swagger 模式 12. .fileOverride() // 覆盖已生成文件 13. .outputDir(FILE_URL+"\\order-service\\src\\main\\java"); // 指定输出目录 14. }) 15. .packageConfig(builder -> { 16. builder.parent("com.jiuxiao") // 设置父包名 17. //.moduleName("mybatisplus") // 设置父包模块名 18. //.entity("pojo") // 设置实体类包名 19. .pathInfo(Collections.singletonMap(OutputFile.mapperXml, FILE_URL+"\\order-service\\src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径 20. }) 21. .strategyConfig(builder -> { 22. builder.addInclude("t_order")// 设置需要生成的表名 23. .addTablePrefix("t_", "c_") // 设置过滤表前缀 24. .entityBuilder().enableLombok() // 开启lombok 25. .enableTableFieldAnnotation() // 开启@tableField 26. .controllerBuilder().enableRestStyle() // 开启@RestController 27. .serviceBuilder().formatServiceFileName("%sService") // 设置接口名称 28. .formatServiceImplFileName("%sServiceImpl").build(); // 设置接口实现类名称 29. }) 30. .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 31. .execute(); 32. } 33.