为什么要学习MybatisPlus?
可以节省编写大量CRUD时间
简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 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
步骤:
- 创建数据库 mybatis_plus
真实开发中:version(乐观锁),deleted(逻辑删除),gmt_create(创建时间),gmt_modified(修改时间)
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');
-
新建初始化SpringBoot项目
-
导入依赖
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus是自己开发的-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis与mybatis-plus,会有版本冲突问题
- 连接数据库
配置文件
#mysql 5
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- 传统mybatis与mybaits-plus比较
- pojo-mapper-使用
- pojo-dao(连接mybatis,配置mapper.xml)-service-controller
pojo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
private String email;
}
mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ji.pojo.User;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
//在对应的Mapper上面继承基本的类baseMapper即可
@Repository
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD操作已经编写完成
//不需要像以前一样配置一大堆文件了
}
主启动类
需要在主启动类上扫描我们mapper包下的所有接口
@SpringBootApplication
@MapperScan("com.ji.mapper")//扫面mapper文件夹,类似于mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
测试类使用
@SpringBootTest
class MybatisPlusApplicationTests {
//继承了BaseMapper,所有的方法都来自父类
//我们也可以编写自己的扩展方法
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}
测试结果
思考问题?
- Sql是谁帮我们写的? mybatis-plus
- 方法从哪里来的? mybatis-plus
配置日志
我们现在所有的sql是不可见的,如果想要知道是如何运行的以便于开发时的调试,就需要配置日志(上线时去掉即可)
#配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置后就可以看到这个自动生成的SQL
CRUD扩展
- insert
@Test
void contextInsert() {
User user = new User();
user.setName("xxx");
user.setAge(7);
user.setEmail("351as6541d65as1d3");
int x = userMapper.insert(user); //自动生成id
System.out.println(x); //受影响的行数
System.out.println(user); //发现,id会自动回填
}
数据库插入的id默认值为全局唯一id
private Long id; //对应数据库中的主键(uuid,自增id,雪花算法,zookeeper生成,redis生成)
主键生成策略
- 数据库自增长序列或字段
- UUID
- UUID的变种
- Redis生成ID
- Twitter的snowflake算法
- 利用zookeeper生成唯一ID
- TiDB的主键
Twitter的snowflake算法: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
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
缺点:
1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,在算法上要解决时间回拨的问题。
@TableId来配置主键生成策略
默认ID_WORKER 全局唯一id
主键自增(数据库id也必须是自增的)
@TableId(type = IdType.AUTO)
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID(如果不输入就会为null)
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
- update
@Test
public void contextUpdate() {
User user = new User();
//根据条件自动拼接动态sql
user.setId((long) 7);
user.setName("mupdate");
user.setAge(77);
user.setEmail("351111");
int x = userMapper.updateById(user);
System.out.println(x);
}
- 自动填充
创建时间,修改时间这些操作一般都是自动化完成的,不需要手动更新
gmt_create,gmt_modified几乎所有的表都要配置,而且需要自动化
方式一:数据库级别(不建议使用)
-
在表中新增字段create_time,update_time
-
实体类中增加相应字段
-
再次更新时查看即可
方式二:代码级别
-
删除数据库的默认值,更新操作
-
实体类字段属性上增加注解
-
编写处理器处理注解即可
@Slf4j
@Component //一定要把处理器加到ioc容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
- 乐观锁
乐观锁:十分乐观,总是认为不会出现问题,无论干什么都不会加锁,如果出现问题,就再次更新值测试(version)
悲观锁:十分悲观,总是认为总是会出现问题,无论干什么都会上锁,再去操作,影响性能
OptimisticLockerInnerInterceptor:当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
1.先查询获得版本号
2.更新时增加版本号
3.如果版本号不对就更新失败
update user set name='Liuyunsan',version = version +1
where id=2 and version =1
- 数据库中增加version字段
- 实体类增加对应字段以及注解
- 注册组件
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.ji.mapper")//扫面mapper文件夹,类似于mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
public class MybatisPlusConfig {
//配置乐观锁插件
@Bean
public OptimisticLockerInterceptor mybatisPlusInterceptor() {
return new OptimisticLockerInterceptor();
}
}
- 测试
//乐观锁成功
@Test
public void contextSuccess() {
//查询用户信息
User user1 = userMapper.selectById(1);
//修改用户信息
user1.setName("jsp");
user1.setEmail("777");
//执行更新操作
int i = userMapper.updateById(user1);
}
//乐观锁失败
@Test
public void contextFail() {
User user1 = userMapper.selectById(1);
user1.setName("jsp777");
user1.setEmail("777777");
User user2 = userMapper.selectById(1);
user2.setName("jsp888");
user2.setEmail("888888");
userMapper.updateById(user2);
//自旋锁多次提交
userMapper.updateById(user1); //如果没有乐观锁就会覆盖插队线程的值
}
- 查询操作
//查询单个用户
@Test
public void testSelect(){
User user = userMapper.selectById(1);
System.out.println(user);
}
//查询多个用户
@Test
public void testSelectServel(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user : users) {
System.out.println(user);
}
}
//条件查询
@Test
public void testCondition(){
HashMap<String, Object> map = new HashMap<>();
//自定义查询
map.put("name","流云散");
map.put("age",18);
List<User> users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
- 分页查询
-
原始 limit 进行分页
-
pageHelper第三方插件
-
mybatis-plus内置插件
配置拦截器组件
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.ji.mapper")//扫面mapper文件夹,类似于mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
public class MybatisPlusConfig {
//配置乐观锁插件
@Bean
public OptimisticLockerInterceptor mybatisPlusInterceptor() {
return new OptimisticLockerInterceptor();
}
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
测试
@Test
public void testPage(){
//参数一当前页
//参数二页面大小
Page<User> objectPage = new Page<>(1,5);
userMapper.selectPage(objectPage, null);
List<User> records = objectPage.getRecords();
for (User record : records) {
System.out.println(record);
}
}
- 删除操作
- 基本删除操作(物理删除:数据库中直接移除)
@Test
//删除
public void testDeleteById(){
//通过id
long x=1;
userMapper.deleteById(x);
//批量删除
userMapper.deleteBatchIds(Arrays.asList(1,3,5,7,9));
HashMap<String, Object> map = new HashMap<>();
//自定义查询
map.put("name","流云散");
map.put("age",18);
userMapper.deleteByMap(map);
}
-
逻辑删除(数据库中没有被移除,通过变量使他生效)
管理员可以查看被删除的记录,防止数据丢失,类似于回收站 -
数据库增加delete字段
-
实体类中增加属性
-
配置
- 测试
//逻辑删除
//字段必须时deleted
@Test
public void testDeleteLogic(){
//通过id
long x=2;
userMapper.deleteById(x);
}
性能分析插件
在平时开发中,如果遇到一些慢sql,可以通过mybatis-plus中的性能分析插件,如果超过这个时间就停止运行并抛出异常
作用:性能分析拦截器,用于输出每条SQL语句及其执行时间
- 导入插件
//性能分析插件
@Bean
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//设置sql执行最大时间,如果超过了就不执行
performanceInterceptor.setFormat(true);//开启格式化支持
return new PerformanceInterceptor();
}
spring.profiles.active=dev
- 测试使用
//查询单个用户
@Test
public void testSelect(){
User user = userMapper.selectById(3);
System.out.println(user);
}
条件构造器wrapper
复杂sql可以使用wrapper来替代
测试:
@Test
void contextLoads() {
//查询name不为空并且邮箱不为空的用户,年龄大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",12);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
@Test
void contextLoads1() {
//查询name等于"流云散"
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","流云散");
User user = userMapper.selectOne(wrapper); //查询一个数据,多个使用list或者map
System.out.println(user);
}
@Test
void contextLoads2() {
//查询age在20-30岁之间的用户数量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
Integer integer = userMapper.selectCount(wrapper);
System.out.println(integer);
}
//模糊查询
@Test
void contextLoads3() {
//名字不包含
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.notLike("name","e") //%e%
.likeRight("email","t"); //t%
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
for (Map<String, Object> map : maps) {
System.out.println(map);
}
}
@Test
void contextLoads4() {
//名字不包含
QueryWrapper<User> wrapper = new QueryWrapper<>();
//id 在子查询中查出来
//SELECT id,name,age,email,create_time,update_time,version,deleted
// FROM user
// WHERE deleted=0 AND id IN (select id from `user` where id < 3)
wrapper.inSql("id","select id from `user` where id < 3" );
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
//排序
@Test
void contextLoads5() {
//通过id进行排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
dao,pojo,service,controller自动生成代码
- 导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}