谷粒学院-2-mybatisplus
一、参考文档
参考教程:http://mp.baomidou.com/guide/
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化 开发、提高效率而生。
快速开始参考:http://mp.baomidou.com/guide/quick-start.html
测试项目: mybatis_plus
数据库:mybatis_plus
二、数据库准备工作
先创建mybatis_plus
数据库
在内运行脚本
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
插入数据
INSERT INTO USER (id, NAME, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
结果:
三、流程:
导入依赖
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
数据源配置
# 数据源
# 数据库驱动在mysql8之前使用com.mysql.cj.jdbc.Driver,之后使用com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Spring Boot 2.1 集成了 8.0版本的jdbc驱动,这个版本的 jdbc 驱动需要添加这个后缀
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
创建pojo类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
创建包 mapper 编写Mapper 接口
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
System.out.println(userMapper.selectList(null));
}
}
结果:
四、添加日志操作
配置信息
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
出现日志
五、insert操作
@SpringBootTest
class DemoApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
User hell = new User();
hell.setName("hell");
hell.setAge(23);
hell.setEmail("22@qq,com");
int result = userMapper.insert(hell);
//result是代码影响的函数
System.out.println(result);
//可以看到id值自动填入,这是mp帮我们做到的
//注意:数据库插入id值默认为:全局唯一id
System.out.println(hell);
}
}
结果:
六、主键策略
1、全局唯一ID策略
MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID 参考资料:分布式系统唯一ID生成方案汇总:https://www.cnblogs.com/haoxinyue/p/5208136.html
2、自增策略
流程:
创建数据库时设置id自增
创建pojo类时
@TableId(type = IdType.AUTO)
private Long id;
结果:
其他主键策略
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
* 用户自己输入
*/
NONE(1),
/**
* 用户输入ID
* 用户自己输入
*/
INPUT(2),
/**
* 全局唯一ID (idWorker)
* 一般对应Long数字类型
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
七、更新数据操作
流程:
@SpringBootTest
class DemoApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
User hell = new User();
hell.setId(2L);
hell.setName("hell");
hell.setAge(23);
hell.setEmail("22@qq,com");
int result = userMapper.updateById(hell);
//result是代码影响的函数
System.out.println(result);
//可以看到id值自动填入,这是mp帮我们做到的
System.out.println(hell);
}
}
mp实现乐观锁
什么是乐观锁:
是一种解决方案,主要解决多人并发操作时更新丢失问题,对应mysql事务的隔离性
悲观锁:
与乐观锁对应都是解决多人并发操作时的更新丢失问题,但时悲观锁是串行的,简单来说就是我操作数据库的时候别人不能操作数据库
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方法:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
流程:
在数据库中添加版本字段
pojo类中
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
需要在字段自动填充中初始化版本号
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
/*metaObject是元数据对象,就比如在jdbc中query一个数据之后也会得到一个元数据
*这个元数据内包含了查询到的数据在数据库中存储的x和y信息
*/
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
/*给版本号在创建时提供一个初始值1*/
this.setFieldValByName("version",1,metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
然后就直接测试了
但注意,一定要遵守先查后改
的原则,在查的时候得到包含了version信息的user对象,在更新的时候就会去找到这个版本号,确认是否对应,如果一致然后才更新,否则更新失败,而且再更新user时会对version进行+1操作
可见mp的乐观锁主要解决从查到数据到更新数据途中是否有其他人或其他线程修改了数据
/*乐观锁成功*/
@Test
public void testOptimisticLockerSuccess(){
/*查询出用户*/
HashMap<String, Object> map = new HashMap<>();
map.put("name","asdfasdf");
List<User> users = userMapper.selectByMap(map);
System.out.println(users);
/*修改用户*/
for (User user:users){
System.out.println("修改前"+user);
user.setAge(20);
int i = userMapper.updateById(user);
System.out.println(i);
System.out.println("修改后"+user);
}
}
/*乐观锁失败*/
@Test
public void testOptimisticLockerFailures(){
/*查询出用户*/
HashMap<String, Object> map = new HashMap<>();
map.put("name","asdfasdf");
List<User> users = userMapper.selectByMap(map);
System.out.println(users);
/*修改用户*/
for (User user:users){
System.out.println("修改前"+user);
user.setAge(20);
user.setVersion(user.getVersion()-1);
int i = userMapper.updateById(user);
System.out.println(i);
System.out.println("修改后"+user);
}
}
八、自动填充
比如创建时间和更新时间需要自动填充就会很方便
流程:
创建数据库时添加两个字段
在pojo类中
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
创建handle包下面创建一个组件
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
/*metaObject是元数据对象,就比如在jdbc中query一个数据之后也会得到一个元数据
*这个元数据内包含了查询到的数据在数据库中存储的x和y位置信息
*/
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
九、查询操作
1、通过id查询
//查询单个用户
User user = userMapper.selectById(1);
System.out.println(user);
2、通过多个id查询
//查询多个用户
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);
3、条件查询
//条件查询
HashMap<String, Object> map = new HashMap<>();
map.put("name","Tom");
map.put("age",28);
List<User> users1 = userMapper.selectByMap(map);
System.out.println(users1);
4、分页查询
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
流程:
需要添加一个bean到IOC容器中去
//mybatisplus的分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
再分页查询
//分页查询
Page<User> page = new Page<>(1,5);
//selectPage方法后一个参数是wrapper,包装条件
userMapper.selectPage(page, null);
//通过page获取全部的分页数据
List<User> records = page.getRecords();
System.out.println(records);
System.out.println(page.getCurrent()); //当前页
System.out.println(page.getPages()); //有多少页
System.out.println(page.getSize()); //每页多少条记录
System.out.println(page.getTotal()); //总共有多少条记录
System.out.println(page.hasNext()); //是否有下一页
System.out.println(page.hasPrevious()); //是否有上一页
十、删除操作
-
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
-
逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍 旧能看到此条数据记录
1、物理删除:
通过id删除
/*通过id删除*/
@Test
public void deleteById(){
int i = userMapper.deleteById(2L);
System.out.println(i);
}
通过多个id批量删除
/*通过多个id批量删除*/
@Test
public void deleteBatchIds(){
int i = userMapper.deleteBatchIds(Arrays.asList(1L, 3L));
System.out.println(i);
}
条件删除
/*条件删除*/
@Test
public void deleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("id",1418944135407554564L);
int i = userMapper.deleteByMap(map);
System.out.println(i);
}
2、逻辑删除
流程:
在数据库中添加deleted字段
pojo中操作
@TableLogic
@TableField(fill = FieldFill.INSERT)
public Integer deleted;
用自动填充来初始化deleted值
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
/*metaObject是元数据对象,就比如在jdbc中query一个数据之后也会得到一个元数据
*这个元数据内包含了查询到的数据在数据库中存储的x和y信息
*/
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
/*给版本号在创建时提供一个初始值1*/
this.setFieldValByName("version",1,metaObject);
/*给被删除状态设置一个初始值;
* 默认0为没删除,1为删除,
* 可以在配置参数文件中中修改*/
this.setFieldValByName("deleted",0,metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
配置Bean组件
/*mybatisplus的逻辑删除插件*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
测试:
/*通过id删除*/
@Test
public void deleteById(){
int i = userMapper.deleteById(2L);
System.out.println(i);
}
注意:内部的sql语句包含了where deleted = 0,所以没有初始化deleted为0的话是删除不了的,会找不到这个数据
测试删除之后的查询:
MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断
/**
* 测试 逻辑删除后的查询:
* 不包括被逻辑删除的记录
*/
@Test
public void testLogicDeleteSelect() {
User user = new User();
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
逻辑层的删除数据也查不到了
十一、性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间 SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题
主要用于开发环境
流程:
加一个配置bean即可
/*sql性能分析插件*/
@Bean
@Profile({"dev"}) //只有开发环境为dev时才会开启开启这个bean配置
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); //超过此设置的ms的sql不执行
performanceInterceptor.setFormat(true); //sql语句是否格式化,默认为false
return performanceInterceptor;
}
测试:
十二、wrapper实现复杂查询
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : Entity 对象封装操作类,不是用lambda语法
- UpdateWrapper : Update 条件封装,用于Entity对象更新操作
- AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
- LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
1、ge、gt、le、lt、isNull、isNotNull
大于等于、大于、小于等于、小于、为空、非空
@Test
public void testDelete() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.isNull("name")
.ge("age", 12)
.isNotNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("delete return count = " + result);
}
对应sql:
UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL
2、eq、ne
等于、不等于
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?
3、between、notBetween
@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
对应sql:
SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ?
4、allEq
所有条件全部一致
@Test
public void testSelectList() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "Jack");
map.put("age", 20);
queryWrapper.allEq(map);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ?
5、like、notLike、likeLeft、likeRight
@Test
public void testSelectMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.notLike("name", "e")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
maps.forEach(System.out::println);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?
6、in、notIn、inSql、notinSql、exists、notExists
in、notIn:
- notIn("age",{1,2,3})--->age not in (1,2,3)
- notIn("age", 1, 2, 3)--->age not in (1,2,3)
inSql、notinSql:可以实现子查询
- 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
- 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
@Test
public void testSelectObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//queryWrapper.in("id", 1, 2, 3);
queryWrapper.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
objects.forEach(System.out::println);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)
7、or、and
@Test
public void testUpdate1() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
//不调用or则默认为使用 and 连
.or()
.between("age", 20, 30);
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
对应sql:
UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age
BETWEEN ? AND ?
8、嵌套or、嵌套and
这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号
@Test
public void testUpdate2() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or(i -> i.eq("name", "李白").ne("age", 20));
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
9、orderBy、orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 ORDER BY id DESC
10、last
直接拼接到 sql 的最后
只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
@Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
对应sql:
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 limit 1
11、指定要查询的列
@Test
public void testSelectListColumn() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name", "age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
对应sql:
SELECT id,name,age FROM user WHERE deleted=0
12、set、setSql
最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的字段
@Test
public void testUpdateSet() {
//修改值
User user = new User();
user.setAge(99);
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
.setSql(" email = '123@qq.com'");//可以有子查询
int result = userMapper.update(user, userUpdateWrapper);
}
对应sql:
UPDATE user SET age=?, update_time=?, name=?, email = '123@qq.com' WHERE deleted=0 AND name LIKE ?