仅为学习记录,方便回顾复习,如有侵权请联系删除!
Mybatis-Plus
官方网站:https://baomidou.com/
总结:mapper接口继承BaseMapper<T>,主启动类添加@MapperScan注解扫描mapper包,结束。
还有乐观锁、自动填充gmt、逻辑删除、wrapper条件构造、代码生成器
目录
文章目录
介绍
简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑;
- 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作,BaseMapper<T>;
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求,简单的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 操作智能分析阻断,也可自定义拦截规则,预防误操作;
一、快速开始
https://baomidou.com/pages/226c21/#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B
基本思路
1、导入依赖;
2、配置;
3、编写代码;
4、提高扩展技术;
实现步骤
1、创建数据库mybatis_plus
2、创建user表插入数据
USE `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)
);
#真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create(创建时间)、gmt_modified(修改时间)
INSERT INTO USER (id,`name`,age,email) VALUES
(1,'Tom',18,'tom@qq.com'),
(2,'Jerry',20,'jerry@qq.com'),
(3,'Jack',21,'jack@qq.com'),
(4,'Rose',30,'rose@qq.com'),
(5,'Smith',24,'smith@qq.com');
3、初始化项目
使用Spring Initializr新建springboot项目,选择web组件。
4、引入对应依赖
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
注意:使用mybatis-plus可以节省大量代码,不要同时导入mybatis和mybatis-plus,可能存在版本冲突。
5、yml配置数据库连接
注意,mysql 8 的驱动不同,而且需要加上时区的配置
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#url: localhost:3306/数据库名称?运用统一编码=是&是否启用安全认证=否&字符集编码格式=utf8&时区设置=Asia/shanghai
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 9527
useSSL=false 和 true 的区别:
SSL(Secure Sockets Layer 安全套接字协议),在mysql进行连接的时候,如果mysql的版本是5.7之后的版本必须要加上useSSL=false,mysql5.7以及之前的版本则不用进行添加useSSL=false,会默认为false,一般情况下都是使用useSSL=false,尤其是在将项目部署到linux上时,一定要使用useSSL=false!!!,useSSL=true是进行安全验证,一般通过证书或者令牌什么的,useSSL=false就是通过账号密码进行连接,通常使用useSSL=false!!!
mysql数据库用的是gbk编码,而项目数据库用的是utf-8编码。这时候如果添加了useUnicode=true&characterEncoding=UTF-8
存数据时:
数据库在存放项目数据的时候会先用UTF-8格式将数据解码成字节码,然后再将解码后的字节码重新使用GBK编码存放到数据库中。取数据时:
在从数据库中取数据的时候,数据库会先将数据库中的数据按GBK格式解码成字节码,然后再将解码后的字节码重新按UTF-8格式编码数据,最后再将数据返回给客户端。
6、使用mybatis-plus
6.1 创建pojo类
创建包com/chw/mybatisplus/pojo
创建实体类:com/chw/mybatisplus/pojo/User.java
package com.chw.mybatisplus.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
//常见的数据库中主键自动设置方法有:uuid , 自增id , 雪花算法 , redis生成 , zookeeper生成
private Long id;
private String name;
private Integer age;
private String email;
}
创建mapper接口集成BaseMapper
创建包:com/chw/mybatisplus/mapper
创建接口:com/chw/mybatisplus/mapper/UserMapper.java
package com.chw.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chw.mybatisplus.pojo.User;
import org.springframework.stereotype.Repository;
// BaseMapper<T> 泛型T是对应的pojo类。
@Repository //注册到spring中
public interface UserMapper extends BaseMapper<User> {
//这样所有的crud都编写完了
}
6.3 主启动类添加@MapperScan注解
添加扫描mapper的注解,指定要扫描的mapper包(是java里的包,而不是resources里的mapper映射文件)
创建主启动类
com/chw/mybatisplus/SpringbootMybatisplusApplication.java
package com.chw.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.chw.mybatisplus.mapper")
public class SpringbootMybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisplusApplication.class, args);
}
}
6.4 单元测试
test文件夹下
com.chw.mybatisplus.SpringbootMybatisplusApplicationTests
package com.chw.mybatisplus;
import com.chw.mybatisplus.mapper.UserMapper;
import com.chw.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class SpringbootMybatisplusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
System.out.println("---- selectAll method test 测试查询所有用户的方法");
//selectList 的参数 wrapper 是条件构造器,可以先写null
List<User> userList = userMapper.selectList(null);
//增强for循环遍历
for (User user : userList) {
System.out.println("user = " + user);
}
}
}
二、yml配置mybatisplus日志
application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
再次执行上面的查询方法查看分析日志
三、insert测试
1、测试插入方法及分析日志
test文件夹下
com.chw.mybatisplus.SpringbootMybatisplusApplicationTests
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
User user = new User();
user.setName("zelda");
user.setAge(16);
user.setEmail("zelda@hyrule.com");
//没有设置ID,但mybatisplus会自动生成ID
int result = userMapper.insert(user);
System.out.println("result = " + result);
System.out.println("user = " + user);
}
控制台输出日志
并没有给user设置id,数据库自动插入了id=1552054238408888322。
注意:数据库默认插入的id是全局唯一的id,下面介绍主键生成策略
2、主键生成策略
2.1 雪花算法
mybaits-plus生成id默认采用雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000),几乎保证全球唯一。
雪花算法在工程实现上有单机版本和分布式版本。单机版本如下,分布式版本可以参看美团leaf算法:https://github.com/Meituan-Dianping/Leaf
可以在User类的id属性上加入注解TableId更改和查看策略
//type = IdType.ASSIGN_ID /*ASSIGN_ID表示雪花算法*/
public class User {
//常见的数据库中主键自动设置方法有:uuid , 自增id , 雪花算法 , redis生成 , zookeeper生成
@TableId(type = IdType.ASSIGN_ID, value = "id") //枚举注解,使用ID_WORKER策略,全局唯一ID,不管数据库是否设置自增。
private Long id;
private String name;
private Integer age;
private String email;
}
2.2 主键自增策略
通过@TableId注解设置自增策略
@TableId(type = IdType.AUTO)
private Long id;
同时数据库设计时,一定要将id设计为自增,这样自增id会设置在最大值上加1。
2.3 其他策略
ctrl+左键点击 IdType进去查看都有哪些枚举
public enum IdType {
AUTO(0), //数据库ID自增,需要确保数据库也设置了ID自增
NONE(1), //未设置主键类型
INPUT(2), //用户输入ID,也可以自定义自动填充插件来填充
ASSIGN_ID(3), //雪花算法。要求插入的id为空值才能自动填充,要求主键类型为number或String
ASSIGN_UUID(4); //uuid。要求插入的id为空值才能自动填充,要求主键类型为String
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
四、update测试
1、测试update及日志分析
test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests
@Autowired
private UserMapper userMapper;
//更新测试
@Test
public void testUpdateById() {
User user = new User();
user.setId(5L);
user.setName("Link");
user.setAge(18);
int i = userMapper.updateById(user);
System.out.println("i = " + i);
}
控制台输出
2、自动填充
创建时间、更新时间等操作可以自动完成,不需手动。
强制要求:gmt_create、gmt_modified字段必须在所有表都要配置,会自动化填充。
gmt表示Greenwich Mean Time,世界时间、格林尼治时间。
2.1 方式一:数据库级别(不建议)
在数据库种添加字段gmt_create、gmt_modified,然后在pojo类中添加这两个属性。
com/chw/mybatisplus/pojo/User.java
import java.util.Date;
public class User {
...
private Date gmtCreate;
private Date gmtModified;
}
2.2 方式二:代码级别
1、先在数据库中删掉刚刚新建的gmt_create和gmt_modified字段,在pojo中删掉刚刚添加的属性。
数据库中创建两个字段:create_time、update_time。类型都是datetime
2、在实体类的成员变量上添加注解@TableField
com/chw/mybatisplus/pojo/User.java
import java.time.LocalDateTime;
public class User {
...
//@TableField提供自动填充功能
@TableField(fill = FieldFill.INSERT,value = "create_time") //FieldFill.INSERT 表示插入时填充create_time字段
private LocalDateTime createTime;
@TableField(fill = FieldFill.UPDATE, value = "update_time") //FieldFill.UPDATE 表示更新时填充update_time字段
private LocalDateTime updateTime;
}
3、编写处理器实现类来处理这个注解
com/chw/mybatisplus/compo/MyMetaObjectHandler.java
package com.chw.mybatisplus.compo;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j //日志
@Component //注册组件
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时启动
//注意第3个参数的类型必须和createTime的类型保持一致,否则生成null插入,如果是Date,就都设置为Date。
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
}
//更新时启动
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); //起始版本 3.3.0(推荐)
}
}
4、测试执行插入、更新操作,观察效果。
五、乐观锁
1、乐观锁原理
-
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不会上锁!如果出现了问题,就再次更新值加锁处理
-
悲观锁:顾名思义十分悲观,他总是认为无论干什么都会出现问题,所以都会上锁,再操作!
乐观锁(OptimisticLockerInnerInterceptor)机制:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
相当于给每一个记录都加一个version字段。当我们要改记录时,把version字段拿出来看一看,对比一下这个version 有没有在你操作数据时被其他线程更改,如果依然等于oldVersion,你就对数据进行操作同时把 version = newVersion 更新(比如+1),以此你在改数据的途中告诉其他线程不要读了脏数据。
2、乐观锁的应用
2.1 测试update
(1) 数据库添加version字段,int型,默认0,长度10,不自增。
(2) pojo类中添加属性,
com/chw/mybatisplus/pojo/User.java
...
public class User {
...
@Version //开启乐观锁支持,一个类中只能有一个@Version注解
private int version;
}
@Version说明:
- 仅支持的数据类型:int , Integer , long , Long , Timestamp , LocalDateTime;
- 整数类型下 newVersion = oldVersion + 1 ;
- newVersion 会回写到entity中;
- 仅支持 updateById(id) 与 update(entity , wrapper) 方法;
- 在 update(entity , wrapper) 方法下,wrapper 不能复用;
(3) 在config下注册组件,开启乐观锁拦截器
com/chw/mybatisplus/config/MybatisPlusConfig.java
package com.chw.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement //开启事务
@Configuration //配置类注解
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁拦截器插件
return mybatisPlusInterceptor;
}
}
(4) 测试乐观锁
test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests
@Autowired
private UserMapper userMapper;
//测试乐观锁
@Test
public void testOptimisticLocker() {
//1. 查询用户信息
User user = userMapper.selectById(1L);
//2. 修改用户信息
user.setEmail("123@qq.com");
user.setName("小可爱");
//3. 更新操作
userMapper.updateById(user);
}
控制台输出结果
(5) 模拟多线程下乐观锁失败案例
test文件夹下com.chw.mybatisplus.SpringbootMybatisplusApplicationTests
@Autowired
private UserMapper userMapper;
//模拟多线程下乐观锁失败
@Test
public void testOptimisticLockerFail() {
//模拟多线程
User user1 = userMapper.selectById(3L);
user1.setEmail("333@qq.com");
user1.setName("波克布林"); //对线程1修改值
//user1里已经有了oldVersion值
//线程2插队
User user2 = userMapper.selectById(3L);
user2.setEmail("thread2@qq.com");
user2.setName("莫力布林");
userMapper.updateById(user2);//线程2抢先提交
//user2更新了Version值
userMapper.updateById(user1);//线程1失败,乐观锁防止了脏数据存在,如果没有乐观锁,线程2的修改就会被覆盖
}
控制台输出
六、select测试
1、根据一个id查询一条数据
@Autowired
private UserMapper userMapper;
@Test
public void testSelectBatchId() {
User user = userMapper.selectById(1L);
System.out.println("user = " + user);
}
//控制台输出
==> Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id=?
==> Parameters: 1(Long)
<== Columns: id, name, age, email, create_time, update_time, version
<== Row: 1, 小可爱, 18, 123@qq.com, null, 2022-07-27 08:18:49, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@358ab600]
user = User(id=1, name=小可爱, age=18, email=123@qq.com, createTime=null, updateTime=2022-07-27T08:18:49, version=1)
2、根据多个id查询多条数据
@Autowired
private UserMapper userMapper;
@Test
public void testSelectBatchIds() {
//Arrays.asList()创建了一个固定大小的集合
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));//selectBatchIds(Collection<? extends Serializable> idList);
users.forEach(System.out::println);
}
//控制台输出
==> Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Integer), 2(Integer), 3(Integer)
<== Columns: id, name, age, email, create_time, update_time, version
<== Row: 1, 小可爱, 18, 123@qq.com, null, 2022-07-27 08:18:49, 1
<== Row: 2, Jerry, 20, jerry@qq.com, null, null, 0
<== Row: 3, 莫力布林, 21, thread2@qq.com, null, 2022-07-27 08:26:32, 1
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44065156]
User(id=1, name=小可爱, age=18, email=123@qq.com, createTime=null, updateTime=2022-07-27T08:18:49, version=1)
User(id=2, name=Jerry, age=20, email=jerry@qq.com, createTime=null, updateTime=null, version=0)
User(id=3, name=莫力布林, age=21, email=thread2@qq.com, createTime=null, updateTime=2022-07-27T08:26:32, version=1)
两个方法的源码
//selectBatchIds(..)方法源码
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
//Arrays.asList(..)方法源码
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
日志分析
sql语句用了 in ( , , )
3、条件查询 and
@Autowired
private UserMapper userMapper;
@Test
public void testSelectByMap() {
HashMap<String, Object> map = new HashMap<>();
//定义查询条件
map.put("name", "zelda");
map.put("age", 3);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
4、分页查询
4.1 配置拦截器
com/chw/mybatisplus/config/MybatisPlusConfig.java
package com.chw.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement //开启事务
@Configuration //配置类注解
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//乐观锁拦截器插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//分页插件拦截器,DbType.MYSQL表示MySQL数据库系统
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
4.2 测试使用page对象
@Autowired
private UserMapper userMapper;
//测试分页查询
@Test
public void testPage() {
//添加分页插件后,会注册一个page对象,构造器参数为 (当前页,每页条数)
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);//分页查询
List<User> records = page.getRecords(); //获取分页后的数据
records.forEach(System.out::println); //打印
System.out.println(page.getTotal());//获取记录总数
}
测试 Page<User> page = new Page<>(2 , 5);
LIMIT ? , ? 即 LIMIT 初始位置 , 记录数。
- 初始位置:从哪条记录开始显示。第一条记录的位置是 0,第二条记录的位置是 1;
- 记录数:表示显示记录的条数;
LIMIT 5 , 5 则表示从第6条数据开始显示,显示5条数据。
七、delete测试
1、常见删除
1.1 根据id删除
@Test
public void testDeleteById() {
userMapper.deleteById(1552054238408888322L);
// userMapper.delete(null); //表示删除全部
}
2、逻辑删除
物理删除:在数据库中移除;
逻辑删除:在数据库中并没有移除,而是在通过改变指定字段的值来标记它失效;
逻辑删除可以用于防止数据丢失,管理员可以查看被删除的记录,用户没法查看。
2.1 在数据库中添加deleted字段
字段名称deleted,数据类型int,默认值是0,长度为1,注释为"逻辑删除"
2.2 在实体类中同步该属性,添加逻辑删除注解 @TableLogic
com/chw/mybatisplus/pojo/User.java
...
public class User {
...
@TableLogic //逻辑删除
private Integer deleted;
}
2.3 yml编写逻辑删除配置
application.yml
mybatis-plus:
global-config:
db-config:
#logic-delete-field: flag #全局逻辑删除的实体字段名,配置之后可以不加2.2的注解@TableLogic
logic-delete-value: 1 #逻辑已删除的值(默认1表示逻辑已删除)
logic-not-delete-value: 0 #逻辑未删除的值(默认0表示逻辑未删除)
2.4 测试逻辑删除
执行删除操作,但实际上是执行更新操作,把deleted字段改为1了
@Autowired
private UserMapper userMapper;
@Test
public void testDeleteById() {
userMapper.deleteById(1L);
// userMapper.delete(null); //表示删除全部
}
控制台输出
数据库效果
如果去执行根据id查询语句查询这个id的值时,后台会自动拼接deleted=0的判断,是的查不到逻辑删除的结果
@Test
public void testSelectBatchId() {
User user = userMapper.selectById(1L);
System.out.println("user = " + user);
}
八、性能分析插件
官方使用的p6spy性能有损耗,推荐使用其他第三方性能分析插件,比如阿里巴巴的Druid。
九、条件构造器Wrapper(重点)
条件构造器官方文档:https://baomidou.com/pages/10c804/
我们之前一直是增删改查,没有一个是带条件的,而这个条件构造器就相当于sql中加了一个where。
-
QueryWrapper 用于生成 select 的 where 条件;
-
UpdateWrapper 用于生成 update 的 where 条件;
查询多条记录与单条记录
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// 查询多条记录
// 一个复杂的查询比如:查询name不为空、邮箱为rose@qq.com、年龄大于等于20的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
//链式编程 添加查询条件
wrapper.isNotNull("name")
.eq("email", "rose@qq.com")
.ge("age", 20);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
// 查询单条记录
QueryWrapper<User> wrapper2 = new QueryWrapper<>();
wrapper2.isNotNull("name")
.eq("email", "rose@qq.com")
.eq("age", 28);
User user = userMapper.selectOne(wrapper2); //只查询一个,出现多个结果会报错
//出现多个结果时报错内容:
//TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
System.out.println("user = " + user);
}
控制台输出
日志分析,就是where中多添加了一个条件
知识点回忆:Lombok的@Accessors注解——开启链式编程
部分方法说明
-
eq:等于,equals
-
ne:不等于,not equals
-
gt:大于,greater than
-
ge:大于等于,greater or equals
-
lt:小于,less than
-
le:小于等于,less or equals
-
or:拼接 OR
-
注意事项:
主动调用
or
表示紧接着下一个方法不是用and
连接!(不调用or
则默认为使用and
连接) -
例:
eq("id",1).or().eq("name","老王")
—>id = 1 or name = '老王'
-
区域查询与计数 between() , selectCount()
between
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
- BETWEEN 值1 AND 值2。包括边界,是个闭区间 [ 值1 , 值2 ]
- 例:
between("age", 18, 30)
—>age between 18 and 30
@Autowired
private UserMapper userMapper;
@Test
void test2() {
//查询区间内的记录
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(wrapper);
System.out.println("count = " + count);
}
日志
模糊查询 like
@Autowired
private UserMapper userMapper;
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name",99) //名字中 存在 99
.notLike("name",6) //名字中 不存在 6
.likeRight("email",2) //邮箱 最左边是 2 ---- 2%
.likeLeft("email","m"); //邮箱 最右边是 m ---- %m
//List<Map<String, Object>> list = userMapper.selectMaps(wrapper);
//list.forEach(System.out::println);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
日志
注:
like("name", "王")`--->`name like '%王%'
notLike("name", "王")--->name not like '%王%'
likeLeft("name", "王")--->name like '%王'
likeRight("name", "王")--->name like '王%'
子查询(多表查询)
@Autowired
private UserMapper userMapper;
@Test
public void test4() {
//子查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from 另一个表格的名字 where id < 9");
List<Object> list = userMapper.selectObjs(wrapper);
list.forEach(System.out::println);
}
日志
JDBC Connection [HikariProxyConnection@982565180 wrapping com.mysql.cj.jdbc.ConnectionImpl@39c96e48] will not be managed by Spring
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND (id IN (select id from 另一个表格的名字 where id < 9))
==> Parameters:
inSql
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
- 字段 IN ( sql语句 )
- 例:
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)
排序 orderByAsc/orderByDesc
@Autowired
private UserMapper userMapper;
@Test
public void test5() {
//排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("id"); //根据id升序排列,如果要降序则 orderByDesc()
userMapper.selectList(wrapper).forEach(System.out::println);
}
日志
分组,条件
@Autowired
private UserMapper userMapper;
@Test
public void test6() {
//分组排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.groupBy("version").having("version = 1");
userMapper.selectList(wrapper).forEach(System.out::println);
}
日志
根据version进行分组,且只查询version = 1的记录
sql回顾:
GROUP BY语句
该GROUP BY语句将具有相同值的行分组为汇总行,例如“查找每个国家/地区的客户数量”。
该GROUP BY语句通常与聚合函数 ( COUNT(), MAX(), MIN(), SUM(), AVG()) 一起使用, 以按一列或多列对结果集进行分组。
SELECT COUNT(CustomerID), Country
FROM Customers
GROUP BY Country;
HAVING 子句
语法:
SELECT column_name(s)
FROM table_name
WHERE condition
GROUP BY column_name(s)
HAVING condition
ORDER BY column_name(s);
SELECT COUNT(CustomerID), Country
FROM Customers
GROUP BY Country
HAVING COUNT(CustomerID) > 5;
#列出了每个国家/地区的客户数量。仅包括拥有超过 5 个客户的国家/地区:
更多条件构造方法
https://baomidou.com/pages/10c804/
十、代码自动生成器(重点)
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
1、引入依赖
pom.xml
<!--mp 代码生成器 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--导入swagger-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.20</version>
</dependency>
<!--添加模板引擎thymeleaf,还有freemarker都是模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
2、在任意文件夹新建一个类进行配置
package com.chw;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
//构建一个代码自动生成器对象
AutoGenerator mpg = new AutoGenerator();
// 1、创建全局配置类的对象
GlobalConfig gc = new GlobalConfig();
//获取当前项目路径
String projectPath = System.getProperty("user.dir");
System.out.println("projectPath = " + projectPath);
//自动生成代码存放的路径
gc.setOutputDir(projectPath + "/src/main/java");
//设置 --作者注释
gc.setAuthor("chw");
//是否打开文件夹
gc.setOpen(false);
//是否覆盖已有文件
gc.setFileOverride(false);
//各层文件名称方式,例如: %sAction 生成 UserAction %s占位符
gc.setServiceName("%sService");
//设置日期策略 date类型
gc.setDateType(DateType.ONLY_DATE);
//设置主键策略 雪花算法
gc.setIdType(IdType.ASSIGN_ID);
//设置开启 swagger2 模式
gc.setSwagger2(true);
//把全局配置放入代码生成器
mpg.setGlobalConfig(gc);
// 2、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc); //把数据源配置加入到代码生成器
// 3、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.chw");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
// ... 有默认值,点击查看源码
mpg.setPackageInfo(pc);//包加入代码生成器
// 4、策略配置
StrategyConfig strategy = new StrategyConfig();
//下划线转驼峰命名 表
strategy.setNaming(NamingStrategy.underline_to_camel);
// 下划线转驼峰命名字段
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//实体类是否加上lombok注解
strategy.setEntityLombokModel(true);
//控制层采用RestControllerStyle注解
strategy.setRestControllerStyle(true);
// RequestMapping中 驼峰转连字符 -
strategy.setControllerMappingHyphenStyle(true);
//要映射的数据库表名 (重点)
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
//添加表名前缀
//strategy.setTablePrefix("m_"); //自动拼接上m_
//逻辑删除字段名
strategy.setLogicDeleteFieldName("deleted");
//乐观锁字段名
strategy.setVersionFieldName("version");
// -------自动填充策略
ArrayList<TableFill> fillList = new ArrayList<>();
fillList.add(new TableFill("createTime", FieldFill.INSERT));
fillList.add(new TableFill("updateTime",FieldFill.INSERT_UPDATE));
// 参数是 List<TableFill> 的链表
strategy.setTableFillList(fillList);
mpg.setStrategy(strategy);
//---------------------------------
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
//输出了 静态资源下的 Mapper
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// FreemarkerTemplateEngine模板引擎
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}