目录
MybatisPlus概述
MybatisPlus特性
MP快速入门
第一步:数据库准备
第二步:导入依赖
第三步:编写实体类
第四步:编写mapper接口
第五步:测试
MP实现增删查改功能
插入功能
主键属性使用的注解@TableId
主键生成策略
普通列注解-@TableField
1.成员变量名和数据库字段名名字不一样,且不符合驼峰映射时
2.类的成员变量不需要进行映射,即没有匹配的数据库字段名
删除功能
修改功能
查询功能
MP实现分页查询
QueryWrapper实现基础查询
QueryWrapper常用API
基础查询实现
OR查询
QueryWrapper模糊查询like
模糊查询常用方法
QueryWrapper排序查询
核心方法
QueryWrapper限定字段查询
QueryWrapper实现分页条件查询
LambdaQueryWrapper查询
使用QueryWrapper开发存在的问题
LambdaQueryWrapper实现条件删除和更新操作
自定义接口
MP实现Service封装
实现流程
快速使用
一.自定义一个接口继承IService接口
二.自定义一个类继承ServiceImpl类和实现自定义的接口
MP封装Service实现CRUD操作
使用MybatisX生成代码
逻辑删除
添加逻辑删除字段
编辑 配置MP让其走逻辑删除
使用@TableLogic注解指定逻辑删除的字段
测试
乐观锁
乐观锁在数据库中有什么优势?
使用乐观锁
一.给数据库表添加一个乐观锁字段
二.在配置类中的插件bean添加乐观锁
三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)
四.测试
实现字段自动填充
一.添加成员变量
二.配置拦截器实现MetaObjectHandler
三.测试
MybatisPlus概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生(提供了快速使用mybatis的方式)。
官网:MyBatis-Plus 或 Redirect
MybatisPlus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
MP快速入门
第一步:数据库准备
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for tb_user 没有给自增
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_name` varchar(255) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`name` varchar(255) DEFAULT NULL,`age` int(11) DEFAULT NULL,`email` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` VALUES (1, '赵一伤', '123456', 'zys', 19, 'zys@itcast.cn');
INSERT INTO `tb_user` VALUES (2, '钱二败', '123456', 'qes', 18, 'qes@itcast.cn');
INSERT INTO `tb_user` VALUES (3, '孙三毁', '123456', 'ssh', 20, 'ssh@itcast.cn');
INSERT INTO `tb_user` VALUES (4, '李四摧', '123456', 'lsc', 20, 'lsc@itcast.cn');
INSERT INTO `tb_user` VALUES (5, '周五输', '123456', 'zws', 20, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (6, '吴六破', '123456', 'wlp', 21, 'wlp@itcast.cn');
INSERT INTO `tb_user` VALUES (7, '郑七灭', '123456', 'zqm', 22, 'zqm@itcast.cn');
INSERT INTO `tb_user` VALUES (8, '王八衰', '123456', 'wbs', 22, 'wbs@itcast.cn');
INSERT INTO `tb_user` VALUES (9, '张无忌', '123456', 'zwj', 25, 'zwj@itcast.cn');
INSERT INTO `tb_user` VALUES (10, '赵敏', '123456', 'zm', 26, 'zm@itcast.cn');
INSERT INTO `tb_user` VALUES (11, '赵二伤', '123456', 'zes', 25, 'zes@itcast.cn');
INSERT INTO `tb_user` VALUES (12, '赵三伤', '123456', 'zss1', 28, 'zss1@itcast.cn');
INSERT INTO `tb_user` VALUES (13, '赵四伤', '123456', 'zss2', 29, 'zss2@itcast.cn');
INSERT INTO `tb_user` VALUES (14, '赵五伤', '123456', 'zws', 39, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (15, '赵六伤', '123456', 'zls', 29, 'zls@itcast.cn');
INSERT INTO `tb_user` VALUES (16, '赵七伤', '123456', 'zqs', 39, 'zqs@itcast.cn');
COMMIT;SET FOREIGN_KEY_CHECKS = 1;
第二步:导入依赖
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--mybatisplus起步依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>
第三步:在application配置文件中编写数据源配置,连接数据库,并让sql语句打印在终端
#配置数据源
spring:datasource:username: rootpassword: 1234url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第三步:编写实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {private Long id;private String userName;private String password;private String name;private Integer age;private String email;
}
第四步:编写mapper接口
/*** 编写接口继承BaseMapper接口,* 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
第五步:测试
@SpringBootTest
public class TestUserMapper {@Autowiredprivate UserMapper userMapper;@Testpublic void test01(){User user = userMapper.selectById(1L);System.out.println(user);}
}
可以发现usrName成员变量已经自动驼峰映射成use_name,因为mp默认开启驼峰映射
MP实现增删查改功能
插入功能
注意事项:
1.如果主键对应的实体类属性中没有设置主键的生成策略,那么MP自动使用雪花算法为主键生成值,且回填到实体对象下;
2.如果未指定主键生成策略,即使表的主键是主键自增,也不会使用主键自增,因为默认的生成策略是使用算法算法生成一个主键id;
测试
@Testpublic void test02(){User user=User.builder().userName("hhh").email("153@qq.com").age(18).name("a").password("123").build();int insert = userMapper.insert(user);System.out.println(insert);}
可以看到主键id是使用雪花算法生成的一个Long型id,即使我们已经为id主键设置了主键自增
并且这个生成的主键id已经回填到了实体类中
主键属性使用的注解@TableId
使用注解@TableId指定id生成策略,让其使用主键自增,而不是使用雪花算法生成一个不重复的Long型id
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {/*** value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id*/@TableId(value = "id",type = IdType.AUTO)private Long id;private String userName;private String password;private String name;private Integer age;private String email;
}
主键生成策略
MP提供的常用主键生成策略如下:
生成策略 | 应用场景 | 特点 |
---|---|---|
IdType.AUTO | 数据库主键自增(确保数据库设置了 主键自增 否则无效) | 1.使用数据库自带的主键自增值; 2.数据库自增的主键值会回填到实体类中; 3.数据库服务端生成的; |
IdType.ASSIGN_ID | 主键类型为number类型或数字类型String | 1.MP客户端生成的主键值; 2.生成的主键值是数字形式的字符串 3.主键对应的类型可以是数字类型或者数字类型的字符串 4.底层基于雪花算法,让数据库的唯一标识也参与id的生成运算,保证id在分布式环境下,全局唯一(避免id的主键冲突问题); |
IdType.ASSIGN_UUID | 主键类型为 string(包含数字和字母组成) | 1.生成的主键值包含数字和字母组成的字符串; 2.注意事项:如果数据库中主键值是number类型的,可不可用 |
普通列注解-@TableField
注解@TableField作用:
- 指定表中普通字段与实体类属性之间的映射关系;
- 忽略实体类中多余属性与表中字段的映射关系(
@TableField(exist = false)
);
以下情况可以省略:
- 名称一样
- 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)
1.成员变量名和数据库字段名名字不一样,且不符合驼峰映射时
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {/*** value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id*/@TableId(value = "id",type = IdType.AUTO)private Long id;/*** 使用@TableField注解让这个成员变量与字段进行映射*/@TableField(value = "user_name")private String realName;private String password;private String name;private Integer age;private String email;
}
可以看到自动生成的sql语句使用了AS关键字
2.类的成员变量不需要进行映射,即没有匹配的数据库字段名
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {/*** value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id*/@TableId(value = "id",type = IdType.AUTO)private Long id;/*** 使用@TableField注解让这个成员变量与字段进行映射*/@TableField(value = "user_name")private String realName;private String password;private String name;private Integer age;private String email;/*** 让这个成员变量不进行sql语句的赋值*/@TableField(exist = false)private String address;
}
可以看到address成员变量并没有在自动生成的sql语句里显示
删除功能
1.根据id删除
int i = userMapper.deleteById(8L);
2.根据id批量删除
int i = userMapper.deleteBatchIds(Arrays.asList(10L, 11));
3.根据map构造条件,删除
Map<String, Object> map = new HashMap<>();//delete from tb_user where user_name = ? and age = ?
map.put("user_name","itcast");
map.put("age","18");userMapper.deleteByMap(map);
修改功能
@Testpublic void test04(){User user=User.builder().id(2L).password("1277").build();//根据主键id来修改数据库的信息,如果这个对象的成员变量值为null,就不修改数据库的字段名int i = userMapper.updateById(user);System.out.println(i);}
查询功能
MP实现分页查询
MP的分页拦截器并没有自动装配,所以需要我们在配置类自己维护一个分页拦截器的bean
@Configuration
public class MybatisPlusConfig {/*** 用于批量注册mp的插件bean*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//构建批量注册插件的对象MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//构建分页插件对象,并指定mysql数据库PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);//配置每页的大小,-1为无上限paginationInnerInterceptor.setMaxLimit(-1L);//将分页插件注册mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);return mybatisPlusInterceptor;}
}
测试
@Testpublic void test05(){int page=2;int pageSize=4;//构建分页对象IPage<User> pageInfo = new Page<>(page, pageSize);//pageInfo==userIPage-->true,这两个对象是同一个,地址相同//null为无条件查询IPage<User> userIPage = userMapper.selectPage(pageInfo, null);System.out.println(pageInfo==userIPage);//获取分页参数long total = userIPage.getTotal();//获取总记录数long pages = userIPage.getPages();//获取总页数long size = userIPage.getSize();//获取当前页大小long current = userIPage.getCurrent();//获取当前的页数List<User> users = userIPage.getRecords();//获取当前页的对象集合}
改进:只使用一个分页对象
@Testpublic void test05(){int page=2;int pageSize=4;//构建分页对象IPage<User> pageInfo = new Page<>(page, pageSize);//pageInfo==userIPage-->true,这两个对象是同一个,地址相同//null为无条件查询
// IPage<User> userIPage = userMapper.selectPage(pageInfo, null);//TODO:因为这两是同一个对象,所以可以直接不定义一个新对象userMapper.selectPage(pageInfo,null);//获取分页参数long total =pageInfo.getTotal();//获取总记录数long pages =pageInfo.getPages();//获取总页数long size = pageInfo.getSize();//获取当前页大小long current= pageInfo.getCurrent();//获取当前的页数List<User> users =pageInfo.getRecords();//获取当前页的对象集合}
QueryWrapper实现基础查询
QueryWrapper常用API
eq( ) : 等于 = ne( ) : 不等于 <> 或者 != gt( ) : 大于 > ge( ) : 大于等于 >= lt( ) : 小于 < le( ) : 小于等于 <= between ( ) : BETWEEN 值1 AND 值2 notBetween ( ) : NOT BETWEEN 值1 AND 值2 in( ) : in notIn( ) :not in like() : like 模糊查询,左右都加% RightLike() :只有右边加% LeftLike():只有左边加%
sql中反向查询eg:not like != 等等,查询时是不会走索引的;
所以尽量不使用not
基础查询实现
要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
@Testpublic void test06(){//要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;QueryWrapper<User> wrapper = new QueryWrapper<>();//设置查询条件//TODO:条件之间默认使用and关键字//第一个参数是数据库字段名wrapper.like("user_name","伤").eq("password","123456").in("age",Arrays.asList(19,25,29)).orderByDesc("age");List<User> users = userMapper.selectList(wrapper);}
OR查询
查询用户名为"lisi"或者年龄大于23的用户信息;
@Testpublic void test07(){//"lisi"或者年龄大于23的用户信息;QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("user_name","lisi").or()//使用or关键字关联条件,默认失and.gt("age",23);List<User> users = userMapper.selectList(wrapper);}
QueryWrapper模糊查询like
模糊查询常用方法
- like("表列名","条件值"); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
- likeLeft("表列名","条件值"); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
- likeRight("表列名","条件值");作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%
左边模糊
@Testpublic void test08(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.likeLeft("user_name","二");List<User> users = userMapper.selectList(wrapper);}
QueryWrapper排序查询
核心方法
- orderByAsc 升序排序,方法内可传入多个字段
- orderByDesc 降序排序,方法内可传入多个字段
QueryWrapper限定字段查询
MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;
如果不写as realName就无法映射到realName成员变量
mp是直接使用select方法的值进行拼接,这样一来就不会识别到realName成员变量的@TableFileld注解
@Testpublic void test09(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.select("user_name as realName","email").orderByDesc("age");List<User> users = userMapper.selectList(wrapper);System.out.println(users);}
QueryWrapper实现分页条件查询
@Testpublic void test10(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.ge("age",20).orderByDesc("age");//构建分页对象IPage<User> page = new Page<>(2,2);userMapper.selectPage(page, wrapper);//获取当前页的对象List<User> users = page.getRecords();System.out.println(users);long pages = page.getPages();}
LambdaQueryWrapper查询
使用QueryWrapper开发存在的问题
- 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
- 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;
LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;
@Testpublic void test11(){//直接new一个对象//LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();//使用工具类生成一个对象LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();//TODO:数据中的字段名通过类的成员变量名以及成员变量上的注解获取,解耦合wrapper.select(User::getRealName,User::getEmail).like(User::getRealName,"伤").eq(User::getPassword,"123456");List<User> users = userMapper.selectList(wrapper);System.out.println(users);}
LambdaQueryWrapper实现条件删除和更新操作
@Testpublic void test12(){//使用wrapper实现条件删除LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getRealName, "hhh");int delete = userMapper.delete(wrapper);System.out.println(delete);}
@Testpublic void test13(){User user=User.builder().realName("hhh").build();LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getId, 1L);int update = userMapper.update(user, wrapper);}
或者直接使用lambdaUpdate的set方法直接进行赋值
@Testpublic void test14(){//直接使用lambdaUpdateLambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate().eq(User::getId, 1L)//更新条件.set(User::getRealName, "aaa").set(User::getPassword, "1243");userMapper.update(null,wrapper);}
自定义接口
/*** 编写接口继承BaseMapper接口,* 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表*/
@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 分页查询id大于id的用户集合* @param page 分页查询对象,分页拦截器会根据先根据这个分页查询对象来拦截sql语句进行分页查询* 然后将查询到的用户集合会封装到这个分页对象中* @param id id*/IPage<User> findUserGtId(IPage<User>page, @Param("id") Long id);
}
xml文件写在哪呢?
直接在resources目录下创建mapper目录即可
因为mp默认的扫描xml文件是mapper目录下的所有文件
xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper"><select id="findUserGtId" resultType="com.itheima.pojo.User">select id,user_name AS realName,password,name,age,email from tb_user where id>#{id}</select></mapper>
测试
@Testpublic void test15(){IPage<User> page = new Page<>(2, 5);userMapper.findUserGtId(page,3L);//获取当前页的对象集合List<User> users = page.getRecords();System.out.println(users);}
可以看到分页查询拦截器会根据分页对象自动为sql语句添加limit关键字进行分页查询
MP实现Service封装
MybatisPlus为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类; 我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效;
实现流程
- 定义一个服务扩展接口,该接口继承公共接口IService;
- 定义一个服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口;
注意事项:
1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作 2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现
思想:共性的业务代码交给框架封装维护,非共性的业务,在接口UserService定义,然后在当前的服务类下实现;
快速使用
一.自定义一个接口继承IService接口
/*** 自定义一个服务层接口继承公共服务接口,泛型是实体类对象(通过@TableName注解知道要操作的数据表)*/
public interface UserService extends IService<User> {IPage<User> getUserGtId(IPage<User>page,Long id);
}
二.自定义一个类继承ServiceImpl类和实现自定义的接口
/**
* 自定义一个服务层实现类,实现自定义的服务层接口
* 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法
* 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法
* 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了
* 注入的这个mapper成员变量叫baseMapper
*/
/*** 自定义一个服务层实现类,实现自定义的服务层接口* 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法* 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法* 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了* 注入的这个mapper成员变量叫baseMapper*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {/*** 只需要实现UserService接口的方法*/@Overridepublic IPage<User> getUserGtId(IPage<User> page, Long id) {//直接使用ServiceImpl注入的mapper bean即可return baseMapper.findUserGtId(page,3L);}
}
MP封装Service实现CRUD操作
/*** @Description 测试条件查询,且仅返回一个* getOne:sql查询的结果必须为1条或者没有,否则报错 !!!!*/@Testpublic void test2(){LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);wrapper.gt(User::getAge,20);User one = userService.getOne(wrapper);System.out.println(one);}/*** @Description 根据条件批量查询*/@Testpublic void test3(){LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);wrapper.gt(User::getAge,20);List<User> list = userService.list(wrapper);System.out.println(list);}/*** @Description 根据条件批量查询并分页*/@Testpublic void test4(){LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);wrapper.gt(User::getAge,20);//构建分页对象IPage<User> page=new Page<>(2,3);userService.page(page,wrapper);System.out.println(page.getRecords());System.out.println(page.getPages());System.out.println(page.getTotal());}/*** @Description 测试服务层save保存单条操作*/@Testpublic void test5(){User user1 = User.builder().name("wangwu").userName("laowang4").email("444@163.com").age(20).password("333").build();boolean isSuccess = userService.save(user1);System.out.println(isSuccess?"保存成功":"保存失败");}/*** @Description 测试服务层批量保存*/@Testpublic void test6(){User user2 = User.builder().name("wangwu2").userName("laowang2").email("444@163.com").age(20).password("333").build();User user3 = User.builder().name("wangwu3").userName("laowang3").email("444@163.com").age(20).password("333").build();boolean isSuccess = userService.saveBatch(Arrays.asList(user2, user3));System.out.println(isSuccess?"保存成功":"保存失败");}/*** @Description 根据id删除操作*/@Testpublic void test7(){boolean isSuccess = userService.removeById(17l);System.out.println(isSuccess?"保存成功":"保存失败");}/*** @Description 根据条件批量删除*/@Testpublic void test8(){LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);wrapper.gt(User::getId,12).gt(User::getAge,20);boolean remove = userService.remove(wrapper);System.out.println(remove);}/*** @Description 测试根据id更新数据*/@Testpublic void test9(){//UPDATE tb_user SET password=?, t_name=? WHERE id=?User user2 = User.builder().name("wangwu2").password("333").id(3l).build();boolean success = userService.updateById(user2);System.out.println(success);}/*** @Description 测试根据条件批量更新*/@Testpublic void test10(){LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(User.class);//UPDATE tb_user SET age=? WHERE (id IN (?,?,?))wrapper.in(User::getId,Arrays.asList(1l,3l,5l)).set(User::getAge,40);boolean update = userService.update(wrapper);System.out.println(userService);}
@Testpublic void test02(){User user1 = User.builder().name("wangwu2").realName("laowang2").email("444@163.com").age(20).password("333").build();userService.saveOrUpdate(user1);//没有设置主键id就是save(新增操作)User user2 = User.builder().id(user1.getId()).name("wangwu3").realName("laowang3").email("444@163.com").age(20).password("333").build();userService.saveOrUpdate(user2);//设置了主键id就是update(修改操作)}
使用MybatisX生成代码
逻辑删除
给表中新增加一个字段,0代表未删除,1代表以删除
这样一来删除的行依然存在于表中
添加逻辑删除字段
配置MP让其走逻辑删除
#配置数据源
spring:datasource:username: rootpassword: 1234url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:logic-delete-field: deleted #指定逻辑删除的表字段logic-delete-value: 1 #指定成功删除后的值logic-not-delete-value: 0 #指定未被删除的值
使用@TableLogic注解指定逻辑删除的字段
/*** 逻辑删除的字段对于的成员变量*/@TableLogicprivate Integer deleted;
测试
@Testpublic void test01(){User user = userMapper.selectById(1L);System.out.println(user);}
查询时mp给sql语句自动添加 and deleted=0;确保该数据没有没删除
@Testpublic void test03(){int i = userMapper.deleteById(1);System.out.println(i);}
删除时底层sql是修改deleted字段的值
乐观锁
乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;
悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;
乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了某些操作,然后让版本号改变,如果当前线程发现版本号改变,则当前操作失败;
乐观锁在数据库中有什么优势?
避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;
本质上就是在操作前,先获取操作行的version版本号,如果当前的版本号与指定的版本号不同则不进行其他操作,相同才进行操作,操作后就让版本号加一。
使用场景:
1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;
2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;
使用乐观锁
一.给数据库表添加一个乐观锁字段
二.在配置类中的插件bean添加乐观锁
@Configuration
public class MybatisPlusConfig {/*** 用于批量注册mp的插件bean*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//构建批量注册插件的对象MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//构建分页插件对象,并指定mysql数据库PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);//配置每页的大小,-1为无上限paginationInnerInterceptor.setMaxLimit(-1L);//将分页插件注册mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);//配置乐观锁插件OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();mybatisPlusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);return mybatisPlusInterceptor;}
}
三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)
/*** 乐观锁字段对应的成员变量*/@Versionprivate Integer version;
四.测试
@Testpublic void test01(){//先做查询,获取当前对象的version值User dbUser = userMapper.selectById(2L);/*进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同如果没有进行修改,那么version值还是和dbUser对象的version相同*/User user = User.builder().id(dbUser.getId())//修改这个id的数据表数据.realName("abc").version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一.build();userMapper.updateById(user);}
可以发现dbUser当前查询到的version值为3,
进行更新操作时,根据dbUser的version值给entity对象的version属性进行赋值,如果当前数据表中id=2的version值还是3,则说明表数据没有被修改,则entity对象的version值与数据库中的version值相同,就可以进行修改操作,且让version值加一(因为对表数据进行了修改)
如果进行更新的entity对象version属性没有进行赋值,就不会有 and version=? 和set version=?
@Testpublic void test01(){//先做查询,获取当前对象的version值User dbUser = userMapper.selectById(2L);/*进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同如果没有进行修改,那么version值还是和dbUser对象的version相同*/User user = User.builder().id(dbUser.getId())//修改这个id的行.realName("abc")//.version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一.build();userMapper.updateById(user);}
实现字段自动填充
先在我新增加了两个字段,给这两个字段填充值时需要频繁的写new Date()进行赋值,我们可以配置让其自动填充
一.添加成员变量
/*** 插入时给这个成员变量赋值*/@TableField(fill = FieldFill.INSERT)private Date createTime;/*** 插入或者更新时给这个成员变量赋值*/@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;
二.配置拦截器实现MetaObjectHandler
/*** 一个拦截器,在数据准备插入数据库时进行拦截*/
@Component
public class FillHandler implements MetaObjectHandler {/*** 在进行插入数据时进行拦截* @param metaObject 封装了准备插入数据库的entity对象*/@Overridepublic void insertFill(MetaObject metaObject) {//第一个参数时类的成员变量名metaObject.setValue("createTime",new Date());metaObject.setValue("updateTime",new Date());}/*** 在进行更新数据时进行拦截* @param metaObject 封装了准备更新数据库数据的entity对象*/@Overridepublic void updateFill(MetaObject metaObject) {metaObject.setValue("updateTime",new Date());}
}
三.测试
@Testpublic void test02(){User user=User.builder().realName("hah").email("153@qq.com").age(18).name("a").password("123").build();int insert = userMapper.insert(user);System.out.println(insert);}
拦截到的数据,此时createTime和updateTime成员变量没有值
赋值后,再进行插入